Added extension methods and some minor related refactoring.

Switched from WindowManager to extensible WindowSeekers (derived from BaseWindowSeeker).
Window seeking by title implemented and command line parameter added.
This commit is contained in:
Lorenz Cuno Klopfenstein 2010-10-11 02:55:40 +02:00
parent 16e9b66fb2
commit 295b40ece9
14 changed files with 247 additions and 114 deletions

View file

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace OnTopReplica {
/// <summary>
/// Extension methods for IEnumerable.
/// </summary>
static class EnumerableExtensions {
/// <summary>
/// Gets the first element of an enumeration of a default value.
/// </summary>
public static T FirstOrDefault<T>(this IEnumerable<T> collection) {
if (collection == null)
throw new ArgumentNullException();
using (var enumerator = collection.GetEnumerator()) {
if (!enumerator.MoveNext())
return default(T);
else
return enumerator.Current;
}
}
}
}

View file

@ -9,6 +9,7 @@ using System.Collections.Generic;
using OnTopReplica.Native;
using OnTopReplica.Update;
using OnTopReplica.StartupOptions;
using OnTopReplica.WindowSeekers;
namespace OnTopReplica {
@ -20,7 +21,7 @@ namespace OnTopReplica {
Panel _sidePanelContainer;
//Managers
WindowManager _windowManager = new WindowManager();
BaseWindowSeeker _windowSeeker = new TaskWindowSeeker();
MessagePumpManager _msgPumpManager = new MessagePumpManager();
UpdateManager _updateManager = new UpdateManager();
@ -105,6 +106,8 @@ namespace OnTopReplica {
protected override void OnHandleCreated(EventArgs e){
base.OnHandleCreated(e);
_windowSeeker.OwnerHandle = this.Handle;
//Platform specific form initialization
Program.Platform.InitForm(this);
}
@ -372,7 +375,8 @@ namespace OnTopReplica {
_thumbnailPanel.SetThumbnailHandle(handle);
#if DEBUG
Console.WriteLine("Cloning window HWND {0}.", handle.Handle);
string windowClass = WindowMethods.GetWindowClass(handle.Handle);
Console.WriteLine("Cloning window HWND {0} of class {1}.", handle.Handle, windowClass);
#endif
if (region.HasValue)

View file

@ -29,8 +29,9 @@ namespace OnTopReplica {
}
private void Menu_Windows_opening(object sender, CancelEventArgs e) {
_windowManager.Refresh(WindowManager.EnumerationMode.TaskWindows);
WindowListHelper.PopulateMenu(this, _windowManager, (ToolStrip)sender,
_windowSeeker.Refresh();
var menu = (ToolStrip)sender;
menu.PopulateMenu(this, _windowSeeker,
CurrentThumbnailWindowHandle, new EventHandler(Menu_Windows_itemclick));
}

View file

@ -37,6 +37,17 @@ namespace OnTopReplica.Native {
return String.Empty;
}
const int MaxClassLength = 255;
public static string GetWindowClass(IntPtr hwnd) {
var sb = new StringBuilder(MaxClassLength + 1);
RealGetWindowClass(hwnd, sb, MaxClassLength);
return sb.ToString();
}
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint RealGetWindowClass(IntPtr hwnd, [Out] StringBuilder lpString, uint maxCount);
public enum WindowLong {
WndProc = (-4),
HInstance = (-6),

View file

@ -112,6 +112,7 @@
</Compile>
<Compile Include="CloneClickEventArgs.cs" />
<Compile Include="CloseRequestEventArgs.cs" />
<Compile Include="EnumerableExtensions.cs" />
<Compile Include="ExtensionAttribute.cs" />
<Compile Include="MainForm_Features.cs">
<SubType>Form</SubType>
@ -134,6 +135,10 @@
<Compile Include="Native\HookMethods.cs" />
<Compile Include="Native\HotKeyMethods.cs" />
<Compile Include="Native\HT.cs" />
<Compile Include="Pair.cs" />
<Compile Include="WindowSeekers\BaseWindowSeeker.cs" />
<Compile Include="WindowSeekers\ByTitleWindowSeeker.cs" />
<Compile Include="WindowSeekers\TaskWindowSeeker.cs" />
<None Include="Native\ITaskBarList.cs" />
<Compile Include="Native\MessagingMethods.cs" />
<Compile Include="Native\MK.cs" />
@ -273,7 +278,6 @@
<Compile Include="Win32Helper.cs" />
<Compile Include="WindowHandle.cs" />
<Compile Include="WindowListHelper.cs" />
<Compile Include="WindowManager.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Assets\windows.png" />

View file

@ -16,6 +16,6 @@
<EnableSecurityDebugging>false</EnableSecurityDebugging>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<StartArguments>/windowId=459572 /r=20,20,100,80</StartArguments>
<StartArguments>/windowClass="OperaWindowClass"</StartArguments>
</PropertyGroup>
</Project>

15
OnTopReplica/Pair.cs Normal file
View file

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace OnTopReplica {
/// <summary>
/// Simple tuple with two values.
/// </summary>
struct Pair<T1, T2> {
public T1 Item1;
public T2 Item2;
}
}

View file

@ -4,6 +4,7 @@ using System.Windows.Forms;
using OnTopReplica.Properties;
using System.Collections.Generic;
using OnTopReplica.MessagePumpProcessors;
using OnTopReplica.WindowSeekers;
namespace OnTopReplica.SidePanels {
partial class GroupSwitchPanel : SidePanel {
@ -23,8 +24,8 @@ namespace OnTopReplica.SidePanels {
}
private void LoadWindowList() {
var manager = new WindowManager();
manager.Refresh(WindowManager.EnumerationMode.TaskWindows);
var manager = new TaskWindowSeeker();
manager.Refresh();
var imageList = new ImageList();
imageList.ColorDepth = ColorDepth.Depth32Bit;

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.IO;
using OnTopReplica.WindowSeekers;
namespace OnTopReplica.StartupOptions {
@ -94,7 +95,12 @@ namespace OnTopReplica.StartupOptions {
handle = WindowHandle.FromHandle(WindowId.Value);
}
else if (WindowTitle != null) {
//TODO
var seeker = new ByTitleWindowSeeker(WindowTitle) {
OwnerHandle = form.Handle
};
seeker.Refresh();
handle = seeker.Windows.FirstOrDefault();
}
else if (WindowClass != null) {
//TODO

View file

@ -3,8 +3,12 @@ using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using OnTopReplica.Properties;
using OnTopReplica.WindowSeekers;
namespace OnTopReplica {
/// <summary>
/// Extension methods used to apply a window list to a menu.
/// </summary>
static class WindowListHelper {
public class WindowSelectionData {
@ -14,7 +18,15 @@ namespace OnTopReplica {
const int MaxWindowTitleLength = 55;
public static void PopulateMenu(Form ownerForm, WindowManager windowManager, ToolStrip menu,
/// <summary>
/// Populates the menu with a list of windows.
/// </summary>
/// <param name="menu">The menu to populate.</param>
/// <param name="ownerForm">The owning form.</param>
/// <param name="windowManager">The window manager that provides the windows list.</param>
/// <param name="currentHandle">The currently used window (will be checked in the list).</param>
/// <param name="clickHandler">Event handler for clicks on window items.</param>
public static void PopulateMenu(this ToolStrip menu, Form ownerForm, BaseWindowSeeker windowManager,
WindowHandle currentHandle, EventHandler clickHandler) {
var regions = GetRegions();

View file

@ -1,104 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using OnTopReplica.Native;
namespace OnTopReplica {
/// <summary>A helper class that allows you to easily build and keep a list of Windows (in the form of WindowHandle objects).</summary>
public class WindowManager {
List<WindowHandle> _windows = new List<WindowHandle>();
public enum EnumerationMode {
/// <summary>All windows with 'Visible' flag.</summary>
AllVisible,
/// <summary>All top level windows.</summary>
AllTopLevel,
/// <summary>Windows of a task (like Alt+Tab).</summary>
TaskWindows
}
/// <summary>Refreshes the window list.</summary>
public void Refresh(EnumerationMode mode) {
_windows = new List<WindowHandle>();
WindowManagerMethods.EnumWindowsProc proc = null;
switch (mode) {
case EnumerationMode.AllVisible:
proc = new WindowManagerMethods.EnumWindowsProc(EnumWindowProcAll);
break;
case EnumerationMode.AllTopLevel:
proc = new WindowManagerMethods.EnumWindowsProc(EnumWindowProcTopLevel);
break;
case EnumerationMode.TaskWindows:
proc = new WindowManagerMethods.EnumWindowsProc(EnumWindowProcTask);
break;
}
WindowManagerMethods.EnumWindows(proc, IntPtr.Zero);
}
public IEnumerable<WindowHandle> Windows {
get {
return _windows;
}
}
private bool EnumWindowProcAll(IntPtr hwnd, IntPtr lParam) {
if (WindowManagerMethods.IsWindowVisible(hwnd)) {
string title = WindowMethods.GetWindowText(hwnd);
_windows.Add( new WindowHandle(hwnd, title));
}
return true;
}
private bool EnumWindowProcTopLevel(IntPtr hwnd, IntPtr lParam) {
if (WindowManagerMethods.IsWindowVisible(hwnd)) {
//Check if window has no parent
if ((long)WindowManagerMethods.GetParent(hwnd) == 0 && WindowManagerMethods.GetDesktopWindow() != hwnd) {
string title = WindowMethods.GetWindowText(hwnd);
_windows.Add( new WindowHandle(hwnd, title));
}
}
return true;
}
private bool EnumWindowProcTask(IntPtr hwnd, IntPtr lParam) {
//Code taken from: http://www.thescarms.com/VBasic/alttab.aspx
//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
//Reject empty titles
string title = WindowMethods.GetWindowText(hwnd);
if (string.IsNullOrEmpty(title))
return true;
if (WindowManagerMethods.IsWindowVisible(hwnd)) {
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 (((exStyle & WindowMethods.WindowExStyles.ToolWindow) == 0 && !hasOwner) || //unowned non-tool window
((exStyle & WindowMethods.WindowExStyles.AppWindow) == WindowMethods.WindowExStyles.AppWindow && hasOwner)) { //owned application window
_windows.Add(new WindowHandle(hwnd, title));
}
}
}
return true;
}
}
}

View file

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Text;
using OnTopReplica.Native;
namespace OnTopReplica.WindowSeekers {
/// <summary>
/// Base class for window seekers that can populate a list of window handles based on some criteria.
/// </summary>
abstract class BaseWindowSeeker {
List<WindowHandle> _list = new List<WindowHandle>();
/// <summary>
/// Get the matching windows from the last refresh.
/// </summary>
public IEnumerable<WindowHandle> Windows {
get {
return _list;
}
}
/// <summary>
/// Forces a window list refresh.
/// </summary>
public virtual void Refresh() {
_list.Clear();
WindowManagerMethods.EnumWindows(
new WindowManagerMethods.EnumWindowsProc(RefreshCallback),
IntPtr.Zero);
}
private bool RefreshCallback(IntPtr hwnd, IntPtr lParam) {
bool cont = true;
//Skip owner
if (hwnd == OwnerHandle)
return true;
//Extract basic properties
string title = WindowMethods.GetWindowText(hwnd);
if (InspectWindow(hwnd, title, ref cont)) {
//Window has been picked
_list.Add(new WindowHandle(hwnd, title));
}
return cont;
}
/// <summary>
/// Inspects a window and returns whether the window should be listed or not.
/// </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);
/// <summary>
/// Gets or sets the window handle of the owner.
/// </summary>
/// <remarks>
/// Windows with this handle will be automatically skipped.
/// </remarks>
public IntPtr OwnerHandle { get; set; }
}
}

View file

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Text;
using OnTopReplica.Native;
namespace OnTopReplica.WindowSeekers {
/// <summary>
/// Seeks a single window by approximately matching its title.
/// </summary>
/// <remarks>
/// Title search is case-insensitive and matches only the beginning of the windows' titles.
/// </remarks>
class ByTitleWindowSeeker : BaseWindowSeeker {
public ByTitleWindowSeeker(string titleSeekString) {
if (titleSeekString == null)
throw new ArgumentNullException();
TitleMatch = titleSeekString.Trim().ToLower();
}
public string TitleMatch { get; private set; }
protected override bool InspectWindow(IntPtr hwnd, string title, ref bool terminate) {
//Skip empty titles
if (string.IsNullOrEmpty(title))
return false;
//Skip non-top-level windows (skips most internal controls)
bool hasParent = (long)WindowManagerMethods.GetParent(hwnd) != 0;
bool hasOwner = (long)WindowManagerMethods.GetWindow(hwnd, WindowManagerMethods.GetWindowMode.GW_OWNER) != 0;
if (hasParent || hasOwner)
return false;
var modTitle = title.Trim().ToLower();
if (modTitle.StartsWith(TitleMatch)) {
terminate = true; //only one needed
return true;
}
return false;
}
}
}

View file

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Text;
using OnTopReplica.Native;
namespace OnTopReplica.WindowSeekers {
class TaskWindowSeeker : BaseWindowSeeker {
protected override bool InspectWindow(IntPtr hwnd, string title, ref bool terminate) {
//Code taken from: http://www.thescarms.com/VBasic/alttab.aspx
//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
//Reject empty titles
if (string.IsNullOrEmpty(title))
return false;
if (WindowManagerMethods.IsWindowVisible(hwnd)) {
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 (((exStyle & WindowMethods.WindowExStyles.ToolWindow) == 0 && !hasOwner) || //unowned non-tool window
((exStyle & WindowMethods.WindowExStyles.AppWindow) == WindowMethods.WindowExStyles.AppWindow && hasOwner)) { //owned application window
return true;
}
}
}
return false;
}
}
}