Refactored and improved the window seeking classes.

This commit is contained in:
Lorenz Cuno Klopfenstein 2012-11-14 17:31:52 +01:00
parent 7e1daf59d2
commit 56a9007f06
9 changed files with 165 additions and 124 deletions

View file

@ -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" />

View file

@ -119,6 +119,8 @@ namespace OnTopReplica.StartupOptions {
handle = seeker.Windows.FirstOrDefault();
}
//Set any found handle
if (handle != null) {
form.SetThumbnail(handle, Region);
}

View file

@ -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.

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View 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();
}
}

View 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);
}
}

View file

@ -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)
}
}
}
}

View file

@ -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;
}
}
}