How to detect if window is flashing - c#

I'm using FlashWindowEx() to flash an application window when it needs to attract a user's attention. The window caption and taskbar button flashes continuously until the application receives focus. How can I check whether the application is currently flashing (i.e. has not received focus since it was instructed to flash).

Here are two possible solutions. One uses WH_SHELL, and one uses a NativeWindow. You will have to provide your own extension method (FlashWindow()) to start the flashing.
// base class. Two different forms subclass this form to illustrate two
// different solutions.
public class FormFlash : Form {
protected Label lb = new Label { Text = "Not flashing", Dock = DockStyle.Top };
public FormFlash() {
Controls.Add(lb);
Thread t = new Thread(() => {
Thread.Sleep(3000);
if (Form.ActiveForm == this)
SetForegroundWindow(GetDesktopWindow()); // deactivate the current form by setting the desktop as the foreground window
this.FlashWindow(); // call extension method to flash window
lb.BeginInvoke((Action) delegate {
lb.Text = "Flashing";
});
});
t.IsBackground = true;
t.Start();
}
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern IntPtr GetDesktopWindow();
}
// this solution is a bit simpler. Relies on the programmer knowing when the
// flashing started. Uses a NativeWindow to detect when a WM_ACTIVATEAPP
// message happens, that signals the end of the flashing.
class FormFlashNW : FormFlash {
NW nw = null;
public FormFlashNW() {
}
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
nw = new NW(this.Handle, lb);
}
protected override void OnHandleDestroyed(EventArgs e) {
base.OnHandleDestroyed(e);
nw.ReleaseHandle();
}
class NW : NativeWindow {
Label lb = null;
public NW(IntPtr handle, Label lb) {
AssignHandle(handle);
this.lb = lb;
}
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
const int WM_ACTIVATEAPP = 0x1C;
if (m.Msg == WM_ACTIVATEAPP) {
lb.BeginInvoke((Action) delegate {
lb.Text = "Not flashing";
});
}
}
}
}
// this solution is more complicated. Relies on setting up the hook proc.
// The 'isFlashing' bool fires true and false alternating while the flashing
// is active.
public class FormShellHook : FormFlash {
public FormShellHook() {
FlashWindowExListener.Register(this);
FlashWindowExListener.FlashEvent += FlashExListener_FlashEvent;
}
void FlashExListener_FlashEvent(Form f, bool isFlashing) {
if (f == this) {
lb.Text = DateTime.Now.ToLongTimeString() + " is flashing: " + isFlashing;
}
}
}
public class FlashWindowExListener {
private delegate IntPtr CallShellProc(int nCode, IntPtr wParam, IntPtr lParam);
private static CallShellProc procShell = new CallShellProc(ShellProc);
private static Dictionary<IntPtr,Form> forms = new Dictionary<IntPtr,Form>();
private static IntPtr hHook = IntPtr.Zero;
public static event FlashWindowExEventHandler FlashEvent = delegate {};
public delegate void FlashWindowExEventHandler(Form f, bool isFlashing);
static FlashWindowExListener() {
int processID = GetCurrentThreadId();
// we are interested in listening to WH_SHELL events, mainly the HSHELL_REDRAW event.
hHook = SetWindowsHookEx(WH_SHELL, procShell, IntPtr.Zero, processID);
System.Windows.Forms.Application.ApplicationExit += delegate {
UnhookWindowsHookEx(hHook);
};
}
public static void Register(Form f) {
if (f.IsDisposed)
throw new ArgumentException("Cannot use disposed form.");
if (f.Handle == IntPtr.Zero) {
f.HandleCreated += delegate {
forms[f.Handle] = f;
};
}
else
forms[f.Handle] = f;
f.HandleDestroyed += delegate {
Unregister(f);
};
}
public static void Unregister(Form f) {
forms.Remove(f.Handle);
}
private static IntPtr ShellProc(int nCode, IntPtr wParam, IntPtr lParam) {
if (nCode == HSHELL_REDRAW) {
Form f = null;
// seems OK not having to call f.BeginInvoke
if (forms.TryGetValue(wParam, out f))
FlashEvent(f, (int) lParam == 1);
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
private const int WH_SHELL = 10;
private const int HSHELL_REDRAW = 6;
[DllImport("user32.dll")]
private static extern int UnhookWindowsHookEx(IntPtr idHook);
[DllImport("user32.dll")]
private static extern IntPtr SetWindowsHookEx(int idHook, CallShellProc lpfn, IntPtr hInstance, int threadId);
[DllImport("kernel32.dll")]
private static extern int GetCurrentThreadId();
[DllImport("user32.dll")]
private static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);
}

Related

Cannot Populate TreeView Control with Win32 Messages in CSharp

I'm working with TreeView Control in Windows Forms in C#.
Since actual population part of TreeView Control takes a lot of time it freezes the UI. So I'm attempting to do the population using PostMessage Win32 API from a background thread but I found that the Treeview isn't getting inserted with Items.
So I moved the code from background thread to main thread. But then also the Insert Item Code is not working. I got similar code working with TreeView in C++ and trying to do the same thing with C# using interop routines.
I'm not going the usual C# way of treeView1.Nodes.Add("...") because it freezes the UI even if I follow the Delegate method and BackgroundWorker method for populating UI controls from another thread. I'm giving the code I use below. Can some one please help to find the issue with the code.
Also Please note for the TreeView Control I'm using my own simple class derived from TreeView class, where I have overriden the WndProc method to verify the flow of Windows Messages and I can see the messages(TVM_INSERTITEM) are actually getting through but still the item is not getting populated
Also I have got similar interop code working fine from Background Thread for ListView Control but my attempts with TreeView haven't succeeded so far.
Form Class Code
using System;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace UpdateTreeViewFromAnotherThread
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct TVITEM
{
public uint mask;
public IntPtr hItem;
public uint state;
public uint stateMask;
public IntPtr pszText;
public int cchTextMax;
public int iImage;
public int iSelectedImage;
public int cChildren;
public uint lParam;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct TVINSERTSTRUCT
{
public IntPtr hParent;
public IntPtr hInsertAfter;
public TVITEM item;
}
public enum TreeViewInsert
{
TVI_ROOT = -0x10000,
}
[Flags]
public enum TreeViewItemMask
{
TVIF_TEXT = 0x0001,
}
public partial class Form1 : Form
{
const int TV_FIRST = 0x1100;
IntPtr tvInsItemPtr;
TVINSERTSTRUCT tvins;
IntPtr handleTreeView;
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);
public enum TreeViewMessage
{
TVM_INSERTITEM = TV_FIRST + 50,
}
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
handleTreeView = treeView1.Handle;
//treeView1.Nodes.Add("hello");
PopulateTree(handleTreeView);
}
public void PopulateTree(IntPtr handle)
{
tvins = new TVINSERTSTRUCT();
tvins.item.mask = (uint)TreeViewItemMask.TVIF_TEXT;
// Set the text of the item.
string productName = "Product";
string value = productName;
byte[] buffer = new byte[100];
buffer = Encoding.Unicode.GetBytes(value + "\0");
tvins.item.pszText = Marshal.AllocHGlobal(buffer.Length);
Marshal.Copy(buffer, 0, tvins.item.pszText, buffer.Length);
tvins.hParent = IntPtr.Zero;
tvins.hInsertAfter = (IntPtr)(TreeViewInsert.TVI_ROOT);
tvInsItemPtr = Marshal.AllocHGlobal(Marshal.SizeOf(tvins));
Marshal.StructureToPtr(tvins, tvInsItemPtr, true);
PostMessage(treeView1.Handle, (uint)TreeViewMessage.TVM_INSERTITEM, IntPtr.Zero, tvInsItemPtr);
//SendMessage(treeView1.Handle, (int)TreeViewMessage.TVM_INSERTITEM, 0, tvInsItemPtr);
}
}
}
MyTreeView Class Code
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace UpdateTreeViewFromAnotherThread
{
class MyTreeView:TreeView
{
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x1132)
{
TVINSERTSTRUCT anotherTVInsertStruct;
anotherTVInsertStruct = (TVINSERTSTRUCT)Marshal.PtrToStructure(m.LParam, typeof(TVINSERTSTRUCT));
string anotherNodeText = Marshal.PtrToStringAnsi(anotherTVInsertStruct.item.pszText);
}
if(m.Msg == 0x113F)
{
TVITEM anotherTVItem;
anotherTVItem = (TVITEM)Marshal.PtrToStructure(m.LParam, typeof(TVITEM));
string anotherNodeText = Marshal.PtrToStringAnsi(anotherTVItem.pszText);
}
base.WndProc(ref m);
//Trace.WriteLine(m.Msg.ToString() + ", " + m.ToString());
}
}
}
Update_1
Prevented NM_CUSTOMDRAW for Treeview using the below code. Thanks to the code atlink.
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_REFLECT + WM_NOTIFY:
{
NMHDR nmhdr = (NMHDR)m.GetLParam(typeof(NMHDR));
switch ((int)nmhdr.code)
{
case NM_CUSTOMDRAW:
NMTVCUSTOMDRAW nmTvDraw = (NMTVCUSTOMDRAW)m.GetLParam(typeof(NMTVCUSTOMDRAW));
switch (nmTvDraw.nmcd.dwDrawStage)
{
case CDDS_ITEMPREPAINT:
m.Result = (IntPtr)CDRF_DODEFAULT;
break;
}
Marshal.StructureToPtr(nmTvDraw, m.LParam, false);
return;
}
break;
}
}
base.WndProc(ref m);
}
So Now If I change my earlier PopulateTree function (note Thread.Sleep()) and its invocation to a background thread as below it will not freeze the UI during the population process
private void button1_Click(object sender, EventArgs e)
{
handleTreeView = treeView1.Handle;
Thread backgroundThread = new Thread(() => PopulateTree(handleTreeView));
backgroundThread.Start();
}
public void PopulateTree(IntPtr handle)
{
for(int i =0; i< 1000; i++)
{
tvins = new TVINSERTSTRUCT();
tvins.item.mask = (uint)TreeViewItemMask.TVIF_TEXT;
// Set the text of the item.
string productName = "Product_" + i.ToString();
string value = productName;
byte[] buffer = new byte[100];
buffer = Encoding.Unicode.GetBytes(value + "\0");
tvins.item.pszText = Marshal.AllocHGlobal(buffer.Length);
Marshal.Copy(buffer, 0, tvins.item.pszText, buffer.Length);
tvins.hParent = IntPtr.Zero;
tvins.hInsertAfter = (IntPtr)(TreeViewInsert.TVI_ROOT);
tvInsItemPtr = Marshal.AllocHGlobal(Marshal.SizeOf(tvins));
Marshal.StructureToPtr(tvins, tvInsItemPtr, true);
PostMessage(handle, (uint)TreeViewMessage.TVM_INSERTITEM, IntPtr.Zero, tvInsItemPtr);
Thread.Sleep(1000);
}
}
Thanks Jimi and MikiD I was able to produce same non-freezing UI behaviour using the BeginUpdate and BeginInvoke approach. I changed my code as below
private async void button1_Click(object sender, EventArgs e)
{
await Task.Run(() => PopulateTree());
}
private async void PopulateTree()
{
for(int i = 0;i< 1000;i++)
{
treeView1.BeginInvoke( (MethodInvoker)delegate ()
{
treeView1.BeginUpdate();
treeView1.Nodes.Add("Product_" + i.ToString());
treeView1.EndUpdate();
}
);
System.Threading.Thread.Sleep(1000);
}
}

ClipboardInterop content changed fires twice

I am writing a WPF application which monitors Clipboard changes.
I wrote the following class:
public class ClipboardInterop : IDisposable
{
public event EventHandler ClipboardContentChanged;
private void OnClipboardContentChanged()
{
var handlers = ClipboardContentChanged;
if (handlers != null)
{
handlers(this, new EventArgs());
}
}
public static ClipboardInterop GetClipboardInterop(Window window)
{
var hwndSource = PresentationSource.FromVisual(window) as HwndSource;
if (hwndSource == null) return null;
return new ClipboardInterop(hwndSource);
}
private IntPtr _thisHandle;
private IntPtr _nextHandle;
private HwndSource _hwndSource;
public bool IsListening { get; private set; }
private ClipboardInterop(HwndSource hwndSource)
{
_hwndSource = hwndSource;
_thisHandle = hwndSource.Handle;
IsListening = false;
}
public bool StartViewingClipboard()
{
Win32.SetLastError(0);
_nextHandle = Win32.SetClipboardViewer(_thisHandle);
if (_nextHandle == IntPtr.Zero)
{
UInt32 eCode = Win32.GetLastError();
if (eCode != 0)
{
return false;
}
}
_hwndSource.AddHook(HwndSourceHook);
IsListening = true;
return true;
}
public bool StopViewingClipboard()
{
Win32.SetLastError(0);
Win32.ChangeClipboardChain(_thisHandle, _nextHandle);
UInt32 eCode = Win32.GetLastError();
IsListening = false;
return eCode == 0;
}
private IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case Win32.WM_CHANGECBCHAIN:
_nextHandle = lParam;
if (_nextHandle != IntPtr.Zero)
{
Win32.SendMessage(_nextHandle, (UInt32)msg, wParam, lParam);
}
break;
case Win32.WM_DRAWCLIPBOARD:
OnClipboardContentChanged();
if (_nextHandle != IntPtr.Zero)
{
Win32.SendMessage(_nextHandle, (UInt32)msg, wParam, lParam);
}
break;
}
return IntPtr.Zero;
}
public void Dispose()
{
if (IsListening)
StopViewingClipboard();
_hwndSource = null;
_nextHandle = IntPtr.Zero;
_thisHandle = IntPtr.Zero;
}
}
Win32.cs looks like:
internal static class Win32
{
/// <summary>
/// The WM_DRAWCLIPBOARD message notifies a clipboard viewer window that
/// the content of the clipboard has changed.
/// </summary>
internal const int WM_DRAWCLIPBOARD = 0x0308;
/// <summary>
/// A clipboard viewer window receives the WM_CHANGECBCHAIN message when
/// another window is removing itself from the clipboard viewer chain.
/// </summary>
internal const int WM_CHANGECBCHAIN = 0x030D;
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetClipboardViewer(
IntPtr hWndNewViewer);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ChangeClipboardChain(
IntPtr hWndRemove,
IntPtr hWndNewNext);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SendMessage(
IntPtr hWnd,
UInt32 msg,
IntPtr wParam,
IntPtr lParam);
[DllImport("kernel32.dll")]
public static extern void SetLastError(
UInt32 errorCode);
[DllImport("kernel32.dll")]
public static extern UInt32 GetLastError();
}
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
Loaded="MainWindow_OnLoaded">
<Button Content="Toggle" Click="ButtonBase_OnClick"></Button>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private ClipboardInterop _clipboardInterop;
public MainWindow()
{
InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
_clipboardInterop.StopViewingClipboard();
_clipboardInterop.StartViewingClipboard();
}
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
_clipboardInterop = ClipboardInterop.GetClipboardInterop(this);
_clipboardInterop.StartViewingClipboard();
_clipboardInterop.ClipboardContentChanged +=
(o, args) => Debug.WriteLine(DateTime.Now.ToLongTimeString() + " Content changed");
}
}
So now when I start the application all is fine, the event is fired once when I copy text to the clipboard. When I click the button, and copy text to clipboard again, the event is fired twice. Another button click and the event is fired three times. I don't know why this is happening. Can anybody help me out?
Well I modified my code with the help of this, and obviously I forgot to remove the hook in StopViewingClipboard.
Here's the fixed code:
public class ClipboardInterop : IDisposable
{
public event EventHandler ClipboardContentChanged;
private void OnClipboardContentChanged()
{
var handlers = ClipboardContentChanged;
if (handlers != null)
{
handlers(this, new EventArgs());
}
}
public static ClipboardInterop GetClipboardInterop(Window window)
{
var wih = new WindowInteropHelper(window);
var hwndSource = HwndSource.FromHwnd(wih.Handle);
if (hwndSource == null)
{
return null;
}
return new ClipboardInterop(hwndSource);
}
private IntPtr _hWndNextViewer;
private HwndSource _hWndSource;
public bool IsViewing { get; private set; }
private ClipboardInterop(HwndSource hwndSource)
{
_hWndSource = hwndSource;
IsViewing = false;
}
public bool StartViewingClipboard()
{
Win32.SetLastError(0);
_hWndNextViewer = Win32.SetClipboardViewer(_hWndSource.Handle);
if (_hWndNextViewer == IntPtr.Zero)
{
UInt32 eCode = Win32.GetLastError();
if (eCode != 0)
{
return false;
}
}
_hWndSource.AddHook(WinProc);
IsViewing = true;
return true;
}
public bool StopViewingClipboard()
{
Win32.SetLastError(0);
Win32.ChangeClipboardChain(_hWndSource.Handle, _hWndNextViewer);
_hWndNextViewer = IntPtr.Zero;
_hWndSource.RemoveHook(WinProc);
UInt32 eCode = Win32.GetLastError();
IsViewing = false;
return eCode == 0;
}
private IntPtr WinProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case Win32.WM_CHANGECBCHAIN:
if (wParam == _hWndNextViewer)
{
_hWndNextViewer = lParam;
}
else if (_hWndNextViewer != IntPtr.Zero)
{
Win32.SendMessage(_hWndNextViewer, msg, wParam, lParam);
}
break;
case Win32.WM_DRAWCLIPBOARD:
OnClipboardContentChanged();
Win32.SendMessage(_hWndNextViewer, msg, wParam, lParam);
break;
}
return IntPtr.Zero;
}
public void Dispose()
{
if (IsViewing)
StopViewingClipboard();
_hWndSource = null;
_hWndNextViewer = IntPtr.Zero;
}
}

Why is my clipboard listener being called twice/application opened twice?

I Wrote a short program in C# that monitors the clipboard. When a certain string enters the clipboard, a program must be opened with Process.Start (depending on the string). Everything works fine, but sometimes the application is being opened twice. I Don't know why this is happening.
namespace ClipboardMonitor {
public class Form1 : System.Windows.Forms.Form {
[DllImport("User32.dll")]
protected static extern int SetClipboardViewer(int hWndNewViewer);
[DllImport("User32.dll", CharSet=CharSet.Auto)]
public static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext);
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
const int SW_HIDE = 0;
const int SW_SHOW = 5;
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetForegroundWindow(IntPtr hWnd);
IntPtr nextClipboardViewer;
private System.ComponentModel.Container components = null;
public Form1() {
InitializeComponent();
nextClipboardViewer = (IntPtr)SetClipboardViewer((int) this.Handle);
var handle = GetConsoleWindow();
ShowWindow(handle, SW_HIDE);
}
protected override void Dispose( bool disposing ) {
ChangeClipboardChain(this.Handle, nextClipboardViewer);
if( disposing ) {
if (components != null) {
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent() {
this.WindowState = FormWindowState.Minimized;
this.ShowInTaskbar = false;
this.Name = "Form1";
}
[STAThread]
static void Main() {
Application.Run(new Form1());
}
protected override void WndProc(ref System.Windows.Forms.Message m) {
const int WM_DRAWCLIPBOARD = 0x308;
const int WM_CHANGECBCHAIN = 0x030D;
switch(m.Msg) {
case WM_DRAWCLIPBOARD:
DisplayClipboardData();
SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam);
break;
case WM_CHANGECBCHAIN:
if (m.WParam == nextClipboardViewer)
nextClipboardViewer = m.LParam;
else
SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam);
break;
default:
base.WndProc(ref m);
break;
}
}
void DisplayClipboardData() {
Thread.Sleep(500);
try {
IDataObject iData = new DataObject();
iData = Clipboard.GetDataObject();
if (iData.GetDataPresent(DataFormats.Text)) {
string path = iData.GetData(DataFormats.Text).ToString();
string[] words = path.Split('_');
if (words[0] == "startLO") {
ProcessStartInfo info = new ProcessStartInfo(words[1]);
Process p = Process.Start(info);
}
} else {
// We doen niets.
}
}
catch(Exception e) {
MessageBox.Show(e.ToString());
}
}
}
One explanation would be that there are multiple clipboard events happening in rapid succession. This is fairly common. You can defend against this with a "settle time" delay. i.e. instead of reacting right away, set a timer or create a thread to handle it "in a little while". As more events come in, keep deferring the settletime. when the settletime finally expires, the timer or thread is allowed to actually run your program.

Adding filter boxes to the column headers of a ListView in C# and WinForms

In Windows Explorer (at least in Win7) when you hover the mouse over a column header, a filter box with an arrow appears that lets you filter the results in the ListView, so for example you can only show files starting with "A" or files > 128 MB. Can this feature be enabled in the basic ListView control in C# without subclassing or modifying the ListView?
Here's some code to play with. Add a new class to your project and paste the code shown below. Compile. Drop the new ListViewEx control from the top of the toolbox onto your form. In the form constructor, call the SetHeaderDropdown() method to enable the button. Implement the HeaderDropdown event to return a control to display. For example:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
listViewEx1.SetHeaderDropdown(0, true);
listViewEx1.HeaderDropdown += listViewEx1_HeaderDropdown;
}
void listViewEx1_HeaderDropdown(object sender, ListViewEx.HeaderDropdownArgs e) {
e.Control = new UserControl1();
}
}
The below code has a flaw, the popup is displayed in a form. Which can't be too small and takes the focus away from the main form. Check this answer on hints how to implement a control that can be displayed as a toplevel window without needing a form. The code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
class ListViewEx : ListView {
public class HeaderDropdownArgs : EventArgs {
public int Column { get; set; }
public Control Control { get; set; }
}
public event EventHandler<HeaderDropdownArgs> HeaderDropdown;
public void SetHeaderDropdown(int column, bool enable) {
if (column < 0 || column >= this.Columns.Count) throw new ArgumentOutOfRangeException("column");
while (HeaderDropdowns.Count < this.Columns.Count) HeaderDropdowns.Add(false);
HeaderDropdowns[column] = enable;
if (this.IsHandleCreated) SetDropdown(column, enable);
}
protected void OnHeaderDropdown(int column) {
var handler = HeaderDropdown;
if (handler == null) return;
var args = new HeaderDropdownArgs() { Column = column };
handler(this, args);
if (args.Control == null) return;
var frm = new Form();
frm.FormBorderStyle = FormBorderStyle.FixedSingle;
frm.ShowInTaskbar = false;
frm.ControlBox = false;
args.Control.Location = Point.Empty;
frm.Controls.Add(args.Control);
frm.Load += delegate { frm.MinimumSize = new Size(1, 1); frm.Size = frm.Controls[0].Size; };
frm.Deactivate += delegate { frm.Dispose(); };
frm.StartPosition = FormStartPosition.Manual;
var rc = GetHeaderRect(column);
frm.Location = this.PointToScreen(new Point(rc.Right - SystemInformation.MenuButtonSize.Width, rc.Bottom));
frm.Show(this.FindForm());
}
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
if (this.Columns.Count == 0 || Environment.OSVersion.Version.Major < 6 || HeaderDropdowns == null) return;
for (int col = 0; col < HeaderDropdowns.Count; ++col) {
if (HeaderDropdowns[col]) SetDropdown(col, true);
}
}
private Rectangle GetHeaderRect(int column) {
IntPtr hHeader = SendMessage(this.Handle, LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero);
RECT rc;
SendMessage(hHeader, HDM_GETITEMRECT, (IntPtr)column, out rc);
return new Rectangle(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top);
}
private void SetDropdown(int column, bool enable) {
LVCOLUMN lvc = new LVCOLUMN();
lvc.mask = LVCF_FMT;
lvc.fmt = enable ? LVCFMT_SPLITBUTTON : 0;
IntPtr res = SendMessage(this.Handle, LVM_SETCOLUMN, (IntPtr)column, ref lvc);
}
protected override void WndProc(ref Message m) {
Console.WriteLine(m);
if (m.Msg == WM_NOTIFY) {
var hdr = (NMHDR)Marshal.PtrToStructure(m.LParam, typeof(NMHDR));
if (hdr.code == LVN_COLUMNDROPDOWN) {
var info = (NMLISTVIEW)Marshal.PtrToStructure(m.LParam, typeof(NMLISTVIEW));
OnHeaderDropdown(info.iSubItem);
return;
}
}
base.WndProc(ref m);
}
private List<bool> HeaderDropdowns = new List<bool>();
// Pinvoke
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, ref LVCOLUMN lvc);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, out RECT rc);
[DllImport("user32.dll")]
private static extern IntPtr SetParent(IntPtr hWnd, IntPtr hParent);
private const int LVM_SETCOLUMN = 0x1000 + 96;
private const int LVCF_FMT = 1;
private const int LVCFMT_SPLITBUTTON = 0x1000000;
private const int WM_NOTIFY = 0x204e;
private const int LVN_COLUMNDROPDOWN = -100 - 64;
private const int LVM_GETHEADER = 0x1000 + 31;
private const int HDM_GETITEMRECT = 0x1200 + 7;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct LVCOLUMN {
public uint mask;
public int fmt;
public int cx;
public string pszText;
public int cchTextMax;
public int iSubItem;
public int iImage;
public int iOrder;
public int cxMin;
public int cxDefault;
public int cxIdeal;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct POINT {
public int x, y;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct RECT {
public int left, top, right, bottom;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct NMHDR {
public IntPtr hwndFrom;
public IntPtr idFrom;
public int code;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct NMLISTVIEW {
public NMHDR hdr;
public int iItem;
public int iSubItem;
public uint uNewState;
public uint uOldState;
public uint uChanged;
public POINT ptAction;
public IntPtr lParam;
}
}
It might be tricky to implement the same type of interface, but you could have your ListView respond to the contents of a TextBox by handling the TextBox's TextChanged event and filtering the list based on the contents. If you put the list in a DataTable then filtering will be easy and you can repopulate your ListView each time the filter changes.
Of course this depends on how many items are in your list.

How do I create a message-only window from windows forms?

I'm trying to create a message-only window to receive window messages from an MFC library class, within a winforms application.
I've tried subclassing NativeWindow, and in the constructor requesting a window handle like this:
CreateParams cp = new CreateParams();
cp.Parent = (IntPtr)HWND_MESSAGE;
this.CreateHandle(cp);
but I get a Win32Exception thrown with the message "Error creating window handle". How do I create a message-only window from windows forms? Is using NativeWindow the right approach?
Try that :
[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
static IntPtr HWND_MESSAGE = new IntPtr(-3);
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
SetParent(this.Handle, HWND_MESSAGE);
}
I know this is 7.5 years old, but just in case anyone finds this, I thought I would respond. I used Microsoft's TimerNativeWindow code and removed the timer functionality. I ended up using this approach:
public class MyNativeWindow : NativeWindow
{
private readonly string _caption;
private const int WmClose = 0x0010;
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
private static readonly HandleRef HwndMessage = new HandleRef(null, new IntPtr(-3));
[DllImport("user32.dll", CharSet = CharSet.Auto)]
[ResourceExposure(ResourceScope.None)]
private static extern IntPtr PostMessage(HandleRef hwnd, int msg, int wparam, int lparam);
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
[ResourceExposure(ResourceScope.Process)]
private static extern int GetWindowThreadProcessId(HandleRef hWnd, out int lpdwProcessId);
[DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
[ResourceExposure(ResourceScope.Process)]
private static extern int GetCurrentThreadId();
public MyNativeWindow(string caption)
{
_caption = caption;
}
public bool CreateWindow()
{
if (Handle == IntPtr.Zero)
{
CreateHandle(new CreateParams
{
Style = 0,
ExStyle = 0,
ClassStyle = 0,
Caption = _caption,
Parent = (IntPtr)HwndMessage
});
}
return Handle != IntPtr.Zero;
}
public void DestroyWindow()
{
DestroyWindow(true, IntPtr.Zero);
}
private bool GetInvokeRequired(IntPtr hWnd)
{
if (hWnd == IntPtr.Zero) return false;
int pid;
var hwndThread = GetWindowThreadProcessId(new HandleRef(this, hWnd), out pid);
var currentThread = GetCurrentThreadId();
return (hwndThread != currentThread);
}
private void DestroyWindow(bool destroyHwnd, IntPtr hWnd)
{
if (hWnd == IntPtr.Zero)
{
hWnd = Handle;
}
if (GetInvokeRequired(hWnd))
{
PostMessage(new HandleRef(this, hWnd), WmClose, 0, 0);
return;
}
lock (this)
{
if (destroyHwnd)
{
base.DestroyHandle();
}
}
}
public override void DestroyHandle()
{
DestroyWindow(false, IntPtr.Zero);
base.DestroyHandle();
}
}
I believe that you'll need to also specify a window class.
I fear that you must derive from a Form, and force the window invisible.
Another approach (in the case the class library is modifiable) is to run a message pump without a window (see Application.Run and Application.AddMessageFilter, or if you prefer pinvokes using PeekMessage & Co).
In this case you can send messages using PostThreadMessage by having the thread id which as run Application.Run, but actually you cannot synch with the application message pump thread because it doesn't wait message acknowledge.

Categories

Resources