mirror of
https://github.com/LorenzCK/OnTopReplica.git
synced 2024-05-20 20:33:06 +12:00
Refactored and improved the window seeking classes.
This commit is contained in:
parent
7e1daf59d2
commit
56a9007f06
|
@ -191,6 +191,8 @@
|
|||
<Compile Include="WindowSeekers\BaseWindowSeeker.cs" />
|
||||
<Compile Include="WindowSeekers\ByClassWindowSeeker.cs" />
|
||||
<Compile Include="WindowSeekers\ByTitleWindowSeeker.cs" />
|
||||
<Compile Include="WindowSeekers\IWindowSeeker.cs" />
|
||||
<Compile Include="WindowSeekers\PointBasedWindowSeeker.cs" />
|
||||
<Compile Include="WindowSeekers\RestoreWindowSeeker.cs" />
|
||||
<Compile Include="WindowSeekers\TaskWindowSeeker.cs" />
|
||||
<Compile Include="WindowsFormsExtensions.cs" />
|
||||
|
|
|
@ -119,6 +119,8 @@ namespace OnTopReplica.StartupOptions {
|
|||
|
||||
handle = seeker.Windows.FirstOrDefault();
|
||||
}
|
||||
|
||||
//Set any found handle
|
||||
if (handle != null) {
|
||||
form.SetThumbnail(handle, Region);
|
||||
}
|
||||
|
|
|
@ -6,36 +6,29 @@ using OnTopReplica.Native;
|
|||
namespace OnTopReplica.WindowSeekers {
|
||||
|
||||
/// <summary>
|
||||
/// Base class for window seekers that can populate a list of window handles based on some criteria.
|
||||
/// Base class for window seekers that can populate a list of window handles based on some criteria and with basic filtering.
|
||||
/// </summary>
|
||||
abstract class BaseWindowSeeker {
|
||||
abstract class BaseWindowSeeker : IWindowSeeker {
|
||||
|
||||
IList<WindowHandle> _list = new List<WindowHandle>();
|
||||
#region IWindowSeeker
|
||||
|
||||
/// <summary>
|
||||
/// Get the matching windows from the last refresh.
|
||||
/// </summary>
|
||||
public virtual IList<WindowHandle> Windows {
|
||||
get {
|
||||
return _list;
|
||||
}
|
||||
protected set {
|
||||
_list = value;
|
||||
}
|
||||
public abstract IList<WindowHandle> Windows {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces a window list refresh.
|
||||
/// </summary>
|
||||
public virtual void Refresh() {
|
||||
_list.Clear();
|
||||
|
||||
WindowManagerMethods.EnumWindows(RefreshCallback, IntPtr.Zero);
|
||||
}
|
||||
|
||||
private bool RefreshCallback(IntPtr hwnd, IntPtr lParam) {
|
||||
bool cont = true;
|
||||
#endregion
|
||||
|
||||
private bool RefreshCallback(IntPtr hwnd, IntPtr lParam) {
|
||||
//Skip owner
|
||||
if (hwnd == OwnerHandle)
|
||||
return true;
|
||||
|
@ -45,23 +38,17 @@ namespace OnTopReplica.WindowSeekers {
|
|||
|
||||
//Extract basic properties
|
||||
string title = WindowMethods.GetWindowText(hwnd);
|
||||
var handle = new WindowHandle(hwnd, title);
|
||||
|
||||
if (InspectWindow(hwnd, title, ref cont)) {
|
||||
//Window has been picked
|
||||
_list.Add(new WindowHandle(hwnd, title));
|
||||
}
|
||||
|
||||
return cont;
|
||||
return InspectWindow(handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inspects a window and returns whether the window should be listed or not.
|
||||
/// Inspects a window and return whether inspection should continue.
|
||||
/// </summary>
|
||||
/// <param name="hwnd">Handle of the window.</param>
|
||||
/// <param name="title">Title of the window (if any).</param>
|
||||
/// <param name="terminate">Indicates whether the inspection loop should terminate after this window.</param>
|
||||
/// <returns>True if the window should be listed.</returns>
|
||||
protected abstract bool InspectWindow(IntPtr hwnd, string title, ref bool terminate);
|
||||
/// <param name="handle">Handle of the window.</param>
|
||||
/// <returns>True if inspection should continue. False stops current refresh operation.</returns>
|
||||
protected abstract bool InspectWindow(WindowHandle handle);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the window handle of the owner.
|
||||
|
|
|
@ -8,9 +8,10 @@ namespace OnTopReplica.WindowSeekers {
|
|||
/// Seeks a single window by matching its window class.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Class matching is exact and case-sensititve.
|
||||
/// Class matching is case-sensitive and prefers perfect matches, also accepting
|
||||
/// partial matches (when the class matches the beginning of the target class name).
|
||||
/// </remarks>
|
||||
class ByClassWindowSeeker : BaseWindowSeeker {
|
||||
class ByClassWindowSeeker : PointBasedWindowSeeker {
|
||||
|
||||
public ByClassWindowSeeker(string className) {
|
||||
if (className == null)
|
||||
|
@ -21,15 +22,20 @@ namespace OnTopReplica.WindowSeekers {
|
|||
|
||||
public string ClassName { get; private set; }
|
||||
|
||||
protected override bool InspectWindow(IntPtr hwnd, string title, ref bool terminate) {
|
||||
var wndClass = WindowMethods.GetWindowClass(hwnd);
|
||||
protected override int EvaluatePoints(WindowHandle handle) {
|
||||
if(string.IsNullOrEmpty(handle.Class))
|
||||
return -1;
|
||||
|
||||
if (ClassName.Equals(wndClass, StringComparison.CurrentCulture)) {
|
||||
return true;
|
||||
}
|
||||
int points = 0;
|
||||
|
||||
return false;
|
||||
//Partial match
|
||||
if (handle.Class.StartsWith(ClassName, StringComparison.InvariantCulture))
|
||||
points += 10;
|
||||
|
||||
if (handle.Class.Equals(ClassName, StringComparison.InvariantCulture))
|
||||
points += 10;
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,34 +10,37 @@ namespace OnTopReplica.WindowSeekers {
|
|||
/// <remarks>
|
||||
/// Title search is case-insensitive and matches only the beginning of the windows' titles.
|
||||
/// </remarks>
|
||||
class ByTitleWindowSeeker : BaseWindowSeeker {
|
||||
class ByTitleWindowSeeker : PointBasedWindowSeeker {
|
||||
|
||||
public ByTitleWindowSeeker(string titleSeekString) {
|
||||
if (titleSeekString == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
TitleMatch = titleSeekString.Trim().ToLower();
|
||||
TitleMatch = titleSeekString.Trim();
|
||||
}
|
||||
|
||||
public string TitleMatch { get; private set; }
|
||||
|
||||
protected override bool InspectWindow(IntPtr hwnd, string title, ref bool terminate) {
|
||||
protected override int EvaluatePoints(WindowHandle handle) {
|
||||
//Skip empty titles
|
||||
if (string.IsNullOrEmpty(title))
|
||||
return false;
|
||||
if (string.IsNullOrEmpty(handle.Title))
|
||||
return -1;
|
||||
|
||||
//Skip non top-level windows
|
||||
if (!WindowManagerMethods.IsTopLevel(hwnd))
|
||||
return false;
|
||||
if (!WindowManagerMethods.IsTopLevel(handle.Handle))
|
||||
return -1;
|
||||
|
||||
var modTitle = title.Trim().ToLower();
|
||||
if (modTitle.StartsWith(TitleMatch)) {
|
||||
terminate = true; //only one needed
|
||||
return true;
|
||||
}
|
||||
int points = 0;
|
||||
|
||||
return false;
|
||||
//Give points for partial match
|
||||
if (handle.Title.StartsWith(TitleMatch, StringComparison.InvariantCultureIgnoreCase))
|
||||
points += 10;
|
||||
|
||||
//Give points for exact match
|
||||
if (handle.Title.Equals(TitleMatch, StringComparison.InvariantCultureIgnoreCase))
|
||||
points += 10;
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
23
OnTopReplica/WindowSeekers/IWindowSeeker.cs
Normal file
23
OnTopReplica/WindowSeekers/IWindowSeeker.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace OnTopReplica.WindowSeekers {
|
||||
/// <summary>
|
||||
/// Interface for window seekers.
|
||||
/// </summary>
|
||||
interface IWindowSeeker {
|
||||
|
||||
/// <summary>
|
||||
/// Get the list of matching windows, ordered by priority (optionally).
|
||||
/// </summary>
|
||||
IList<WindowHandle> Windows { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the list of windows.
|
||||
/// </summary>
|
||||
void Refresh();
|
||||
|
||||
}
|
||||
}
|
55
OnTopReplica/WindowSeekers/PointBasedWindowSeeker.cs
Normal file
55
OnTopReplica/WindowSeekers/PointBasedWindowSeeker.cs
Normal file
|
@ -0,0 +1,55 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace OnTopReplica.WindowSeekers {
|
||||
/// <summary>
|
||||
/// Window seeker that uses a point system to get a list of matching windows listed by optimality.
|
||||
/// </summary>
|
||||
abstract class PointBasedWindowSeeker : BaseWindowSeeker {
|
||||
|
||||
IList<WindowHandle> _currentWindowList = new List<WindowHandle>();
|
||||
|
||||
public override IList<WindowHandle> Windows {
|
||||
get {
|
||||
return _currentWindowList;
|
||||
}
|
||||
}
|
||||
|
||||
List<Tuple<int, WindowHandle>> _sortingList = null;
|
||||
|
||||
public override void Refresh() {
|
||||
_sortingList = new List<Tuple<int, WindowHandle>>();
|
||||
|
||||
base.Refresh();
|
||||
|
||||
//Sort and store
|
||||
_currentWindowList = (from t in _sortingList
|
||||
orderby t.Item1 descending
|
||||
select t.Item2).ToList();
|
||||
|
||||
_sortingList = null;
|
||||
}
|
||||
|
||||
protected override bool InspectWindow(WindowHandle handle) {
|
||||
int points = EvaluatePoints(handle);
|
||||
if(points >= 0){
|
||||
_sortingList.Add(new Tuple<int, WindowHandle>(points, handle));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evalutes the points for a window handle.
|
||||
/// </summary>
|
||||
/// <param name="handle">Handle to the window.</param>
|
||||
/// <returns>
|
||||
/// Number of points. Higher points identify better suited windows.
|
||||
/// Windows with negative points are discarded altogether.
|
||||
/// </returns>
|
||||
protected abstract int EvaluatePoints(WindowHandle handle);
|
||||
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ namespace OnTopReplica.WindowSeekers {
|
|||
/// <summary>
|
||||
/// Window seeker that attempts to locate a window to restore (by class, title and ID).
|
||||
/// </summary>
|
||||
class RestoreWindowSeeker : BaseWindowSeeker {
|
||||
class RestoreWindowSeeker : PointBasedWindowSeeker {
|
||||
|
||||
public RestoreWindowSeeker(IntPtr handle, string title, string className){
|
||||
Handle = handle;
|
||||
|
@ -22,94 +22,40 @@ namespace OnTopReplica.WindowSeekers {
|
|||
|
||||
public string Class { get; private set; }
|
||||
|
||||
bool _mustBeOrdered = true;
|
||||
|
||||
public override void Refresh() {
|
||||
//Whenever the window list is refreshed, the list must be reordered
|
||||
_mustBeOrdered = true;
|
||||
_points = new Dictionary<long, int>();
|
||||
|
||||
base.Refresh();
|
||||
}
|
||||
|
||||
Dictionary<long, int> _points = new Dictionary<long, int>();
|
||||
|
||||
protected override bool InspectWindow(IntPtr hwnd, string title, ref bool terminate) {
|
||||
if (!WindowManagerMethods.IsTopLevel(hwnd))
|
||||
return false;
|
||||
protected override int EvaluatePoints(WindowHandle handle) {
|
||||
if (!WindowManagerMethods.IsTopLevel(handle.Handle)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int points = 0;
|
||||
|
||||
//Class exact match
|
||||
if (!string.IsNullOrEmpty(Class)) {
|
||||
string wndClass = WindowMethods.GetWindowClass(hwnd);
|
||||
if (Class.Equals(wndClass, StringComparison.InvariantCulture)) {
|
||||
string wndClass = handle.Class;
|
||||
if (wndClass.StartsWith(Class, StringComparison.InvariantCulture)){
|
||||
points += 10;
|
||||
}
|
||||
}
|
||||
|
||||
//Title match (may not be exact, but let's try)
|
||||
if (!string.IsNullOrEmpty(Title) && !string.IsNullOrEmpty(title)) {
|
||||
if (title.StartsWith(Title, StringComparison.InvariantCultureIgnoreCase)) {
|
||||
if (!string.IsNullOrEmpty(Title) && !string.IsNullOrEmpty(handle.Title)) {
|
||||
if (handle.Title.StartsWith(Title, StringComparison.InvariantCultureIgnoreCase)) {
|
||||
points += 10;
|
||||
}
|
||||
if (title.Equals(Title, StringComparison.InvariantCultureIgnoreCase)) {
|
||||
if (handle.Title.Equals(Title, StringComparison.InvariantCultureIgnoreCase)) {
|
||||
points += 5;
|
||||
}
|
||||
}
|
||||
|
||||
//Handle match (will probably not work, but anyhow)
|
||||
if (Handle != IntPtr.Zero) {
|
||||
if (Handle == hwnd) {
|
||||
if (Handle == handle.Handle) {
|
||||
points += 10;
|
||||
}
|
||||
}
|
||||
|
||||
//Store handle if it matches
|
||||
if (points > 0) {
|
||||
_points.Add(hwnd.ToInt64(), points);
|
||||
}
|
||||
|
||||
//Never store windows in base class list
|
||||
return false;
|
||||
return points;
|
||||
}
|
||||
|
||||
public override IList<WindowHandle> Windows {
|
||||
get {
|
||||
if (_mustBeOrdered) {
|
||||
WindowHandle[] arr = new WindowHandle[base.Windows.Count];
|
||||
base.Windows.CopyTo(arr, 0);
|
||||
Array.Sort<WindowHandle>(arr, new PointComparer(_points));
|
||||
|
||||
//Store ordered array
|
||||
base.Windows = arr;
|
||||
|
||||
_mustBeOrdered = false;
|
||||
}
|
||||
|
||||
return base.Windows;
|
||||
}
|
||||
}
|
||||
|
||||
private class PointComparer : IComparer<WindowHandle> {
|
||||
|
||||
public PointComparer(IDictionary<long, int> pointDict) {
|
||||
_pointDict = pointDict;
|
||||
}
|
||||
|
||||
IDictionary<long, int> _pointDict;
|
||||
|
||||
public int Compare(WindowHandle x, WindowHandle y) {
|
||||
int px = 0;
|
||||
_pointDict.TryGetValue(x.Handle.ToInt64(), out px);
|
||||
int py = 0;
|
||||
_pointDict.TryGetValue(y.Handle.ToInt64(), out py);
|
||||
|
||||
return py.CompareTo(px); //inverse comparison (from max points to min)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,32 +4,49 @@ using System.Text;
|
|||
using OnTopReplica.Native;
|
||||
|
||||
namespace OnTopReplica.WindowSeekers {
|
||||
/// <summary>
|
||||
/// Window seeker that attempts to mimic ALT+TAB behavior in filtering windows to show.
|
||||
/// </summary>
|
||||
class TaskWindowSeeker : BaseWindowSeeker {
|
||||
|
||||
protected override bool InspectWindow(IntPtr hwnd, string title, ref bool terminate) {
|
||||
List<WindowHandle> _list = new List<WindowHandle>();
|
||||
|
||||
public override IList<WindowHandle> Windows {
|
||||
get {
|
||||
return _list;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Refresh() {
|
||||
_list.Clear();
|
||||
|
||||
base.Refresh();
|
||||
}
|
||||
|
||||
protected override bool InspectWindow(WindowHandle handle) {
|
||||
//Code taken from: http://www.thescarms.com/VBasic/alttab.aspx
|
||||
|
||||
//Reject empty titles
|
||||
if (string.IsNullOrEmpty(title))
|
||||
return false;
|
||||
if (string.IsNullOrEmpty(handle.Title))
|
||||
return true;
|
||||
|
||||
//Accept windows that
|
||||
// - are visible
|
||||
// - do not have a parent
|
||||
// - have no owner and are not Tool windows OR
|
||||
// - have an owner and are App windows
|
||||
if ((long)WindowManagerMethods.GetParent(hwnd) == 0) {
|
||||
bool hasOwner = (long)WindowManagerMethods.GetWindow(hwnd, WindowManagerMethods.GetWindowMode.GW_OWNER) != 0;
|
||||
WindowMethods.WindowExStyles exStyle = (WindowMethods.WindowExStyles)WindowMethods.GetWindowLong(hwnd, WindowMethods.WindowLong.ExStyle);
|
||||
if ((long)WindowManagerMethods.GetParent(handle.Handle) == 0) {
|
||||
bool hasOwner = (long)WindowManagerMethods.GetWindow(handle.Handle, WindowManagerMethods.GetWindowMode.GW_OWNER) != 0;
|
||||
WindowMethods.WindowExStyles exStyle = (WindowMethods.WindowExStyles)WindowMethods.GetWindowLong(handle.Handle, WindowMethods.WindowLong.ExStyle);
|
||||
|
||||
if (((exStyle & WindowMethods.WindowExStyles.ToolWindow) == 0 && !hasOwner) || //unowned non-tool window
|
||||
((exStyle & WindowMethods.WindowExStyles.AppWindow) == WindowMethods.WindowExStyles.AppWindow && hasOwner)) { //owned application window
|
||||
return true;
|
||||
|
||||
_list.Add(handle);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue