I am interested in removing the text selection of ComboBoxes with DropDownStyle = DropDown.
When I add/remove or close the DropPown, then the Item is selected.
I am not able to clear the selected text. Do you have some idea how to do this?
This code does not work:
comboBox.SelectionLenght = 0;
comboBox.SelectionStart = comboBox.Text.Legnth;
comboBox.Select(0,0);
I can see that the text is highlighted after this line:
selectedComboBox.Items.Add(redCompetitorName);
You can defer the execution of the Select() method, calling BeginInvoke() in the SelectedIndexChanged event handler (or SelectionChangedCommitted, if you want this to happen only when a User selects an Item manually).
By deferring the execution (this action is enqueued in the message loop), the Select() action is performed only after the ComboBox.Text has been set and highlighted. So your command is not overridden by the default behavior.
private void comboBox_SelectedIndexChanged(object sender, EventArgs e)
{
// Set the caret to the start of the ComboBox text
BeginInvoke(new Action(()=> comboBox.Select(0, 0)));
// OR - set the caret to the end of the text instead
BeginInvoke(new Action(()=> comboBox.Select(int.MaxValue, 0)));
}
This concept applies in other contexts of course.
You can use this method in other situations, when you need to perform an action, in the UI, that is executed only after the current (or the current sequence of the already scheduled actions) has completed.
If you want a more involved solution that prevents all kind of selection highlights in a ComboBox, you can use a Custom Control derived from ComboBox, get the Handle of its Edit Control, use a NativeWindow to intercept its messages. Override WndProc to handle EM_SETSEL and call PostMessage to remove the selection (only when the starting position is > 0, otherwise you risk to get stuck in a weird auto-loop which has an effect that's usually referred to as StackOverflow :).
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
[DesignerCategory("code")]
public class ComboBoxNoFocus : ComboBox
{
IntPtr editHandle = IntPtr.Zero;
private EditNativeWindow editControl = null;
public ComboBoxNoFocus() { }
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
editHandle = GetComboBoxEditInternal(this.Handle);
editControl = new EditNativeWindow(editHandle);
}
public class EditNativeWindow : NativeWindow
{
private const int EM_SETSEL = 0x0B1;
public EditNativeWindow() : this(IntPtr.Zero) { }
public EditNativeWindow(IntPtr handle)
{
if (handle != IntPtr.Zero) {
this.AssignHandle(handle);
}
}
protected override void WndProc(ref Message m)
{
switch (m.Msg) {
case EM_SETSEL:
int pos = m.LParam.ToInt32();
if (pos > 0) {
PostMessage(this.Handle, EM_SETSEL, 0, 0);
}
return;
default:
// Other operations
break;
}
base.WndProc(ref m);
}
}
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
internal static extern bool GetComboBoxInfo(IntPtr hWnd, ref COMBOBOXINFO pcbi);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool PostMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);
[StructLayout(LayoutKind.Sequential)]
internal struct COMBOBOXINFO
{
public int cbSize;
public Rectangle rcItem;
public Rectangle rcButton;
public int buttonState;
public IntPtr hwndCombo;
public IntPtr hwndEdit;
public IntPtr hwndList;
public void Init() => this.cbSize = Marshal.SizeOf<COMBOBOXINFO>();
}
internal static IntPtr GetComboBoxEditInternal(IntPtr cboHandle)
{
var cbInfo = new COMBOBOXINFO();
cbInfo.Init();
GetComboBoxInfo(cboHandle, ref cbInfo);
return cbInfo.hwndEdit;
}
}
Set the focus to another control in your form after a new item is selected, using the SelectedIndexChanged event of the Combobox.
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
button1.Focus();
}
Related
Background:
I have a Forms.ComboBox with a DropDownStyle = DropDown.
I don't use AutoComplete, but I implemented something similar which does not only filter the beginning of the text, but uses a regular expression and shows all items which match the text entered. This works fine.
However, when I type the first letter of a matching item, the ComboBox falls back to its original behavior and sets DroppedDown = true and auto selects the first entry and completes the text to match the selected item (similar to AutoCompleteMode Append). What I want is no auto selection and auto completion.
What I found so far is, that I somehow have to prevent SendMessage() with CB_FINDSTRING of being called and replace CB_FINDSTRING with CB_FINDSTRINGEXACT (MSDN Link).
I think I have to extend the ComboBox class, but I'm not sure which methods I have to override. I'm working with C# .NET Framework v3.5.
Questions:
How do I extend a Windows.Forms.ComboBox to prevent the auto select behavior?
Links:
How can I prevent auto-select in ComboBox on drop-down except for exact matches? (did not help me)
Try this:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Opulos.Core.Win32;
namespace Opulos.Core.UI {
// Extension class to disable the auto-select behavior when a combobox is in DropDown mode.
public static class ComboBoxAutoSelectEx {
public static void AutoSelectOff(this ComboBox combo) {
Data.Register(combo);
}
public static void AutoSelectOn(this ComboBox combo) {
Data data = null;
if (Data.dict.TryGetValue(combo, out data)) {
data.Dispose();
Data.dict.Remove(combo);
}
}
private class Data {
// keep a reference to the native windows so they don't get disposed
internal static Dictionary<ComboBox, Data> dict = new Dictionary<ComboBox, Data>();
// a ComboBox consists of 3 windows (combobox handle, text edit handle and dropdown list handle)
ComboBox combo;
NW nwList = null; // handle to the combobox's dropdown list
NW2 nwEdit = null; // handle to the edit window
internal void Dispose() {
dict.Remove(this.combo);
this.nwList.ReleaseHandle();
this.nwEdit.ReleaseHandle();
}
public static void Register(ComboBox combo) {
if (dict.ContainsKey(combo))
return; // already registered
Data data = new Data() { combo = combo };
Action assign = () => {
if (dict.ContainsKey(combo))
return; // already assigned
COMBOBOXINFO info = COMBOBOXINFO.GetInfo(combo); // new COMBOBOXINFO();
//info.cbSize = Marshal.SizeOf(info);
//COMBOBOXINFO2.SendMessageCb(combo.Handle, 0x164, IntPtr.Zero, out info);
dict[combo] = data;
data.nwList = new NW(combo, info.hwndList);
data.nwEdit = new NW2(info.hwndEdit);
};
if (!combo.IsHandleCreated)
combo.HandleCreated += delegate { assign(); };
else
assign();
combo.HandleDestroyed += delegate {
data.Dispose();
};
}
}
private class NW : NativeWindow {
ComboBox combo;
public NW(ComboBox combo, IntPtr handle) {
this.combo = combo;
AssignHandle(handle);
}
private const int LB_FINDSTRING = 0x018F;
private const int LB_FINDSTRINGEXACT = 0x01A2;
protected override void WndProc(ref Message m) {
if (m.Msg == LB_FINDSTRING) {
m.Msg = LB_FINDSTRINGEXACT;
}
base.WndProc(ref m);
if (m.Msg == LB_FINDSTRINGEXACT) {
String find = Marshal.PtrToStringAuto(m.LParam);
for (int i = 0; i < combo.Items.Count; i++) {
Object item = combo.Items[i];
if (item.Equals(find)) {
m.Result = new IntPtr(i);
break;
}
}
}
}
}
private class NW2 : NativeWindow {
public NW2(IntPtr handle) {
AssignHandle(handle);
}
private const int EM_SETSEL = 0x00B1;
private const int EM_GETSEL = 0x00B0;
protected override void WndProc(ref Message m) {
if (m.Msg == EM_SETSEL) {
// if this code is not here, then the entire combobox text is selected
// which looks ugly, especially when there are multiple combo boxes.
//
// if this method returns immediately, then the caret position is set
// to (0, 0). However, it seems that calling EM_GETSEL has a side effect
// that the caret position is mostly maintained. Sometimes it slips back
// to (0, 0).
SendMessage(Handle, EM_GETSEL, IntPtr.Zero, IntPtr.Zero);
//int selStart = (sel & 0x00ff);
//int selEnd = (sel >> 16) & 0x00ff;
//Debug.WriteLine("EM_GETSEL: " + selStart + " nEnd: " + selEnd);
return;
}
base.WndProc(ref m);
}
[DllImportAttribute("user32.dll", SetLastError=true)]
private static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
}
}
[StructLayout(LayoutKind.Sequential)]
public struct COMBOBOXINFO {
public Int32 cbSize;
public RECT rcItem;
public RECT rcButton;
public int buttonState;
public IntPtr hwndCombo;
public IntPtr hwndEdit;
public IntPtr hwndList;
public static COMBOBOXINFO GetInfo(ComboBox combo) {
COMBOBOXINFO info = new COMBOBOXINFO();
info.cbSize = Marshal.SizeOf(info);
SendMessageCb(combo.Handle, 0x164, IntPtr.Zero, out info);
return info;
}
[DllImport("user32.dll", EntryPoint = "SendMessageW", CharSet = CharSet.Unicode)]
private static extern IntPtr SendMessageCb(IntPtr hWnd, int msg, IntPtr wp, out COMBOBOXINFO lp);
}
//[StructLayout(LayoutKind.Sequential)]
//public struct RECT {
// public int Left;
// public int Top;
// public int Right;
// public int Bottom;
//}
}
I want to leverage machine learning to model a user's intent and potentially automate commonly performed tasks. To do this I would like to have access to a fire-hose of information about user actions and the machine state. To this end, it is my current thinking that getting access to the stream of windows messages is probably the way forward.
I would like to have as much information as is possible, filtering the information to that which is pertinent I would like to leave to the machine learning tool.
How would this be accomplished? (Preferably in C#).
Please assume that I know how to manage and use this large influx of data.
Any help would be gratefully appreciated.
You can use SetWindowsHookEx to set low level hooks to catch (specific) windows messages.
Specifically these hook-ids might be interesting for monitoring:
WH_CALLWNDPROC (4) Installs a hook procedure that monitors messages
before the system sends them to the destination window procedure. For
more information, see the CallWndProc hook procedure.
WH_CALLWNDPROCRET(12) Installs a hook procedure that monitors
messages after they have been processed by the destination window
procedure. For more information, see the CallWndRetProc hook
procedure.
It's been a while since I've implemented it, but as an example I've posted the base class I use to hook specific messages. (For example, I've used it in a global mousewheel trapper, that makes sure my winforms apps behave the same as internet explorer: scroll the control underneath the cursor, instead of the active control).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using Subro.Win32;
namespace Subro
{
/// <summary>
/// Base class to relatively safely register global windows hooks
/// </summary>
public abstract class GlobalHookTrapper : FinalizerBase
{
[DllImport("user32", EntryPoint = "SetWindowsHookExA")]
static extern IntPtr SetWindowsHookEx(int idHook, Delegate lpfn, IntPtr hmod, IntPtr dwThreadId);
[DllImport("user32", EntryPoint = "UnhookWindowsHookEx")]
private static extern int UnhookWindowsHookEx(IntPtr hHook);
[DllImport("user32", EntryPoint = "CallNextHookEx")]
static extern int CallNextHook(IntPtr hHook, int ncode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentThreadId();
IntPtr hook;
public readonly int HookId;
public readonly GlobalHookTypes HookType;
public GlobalHookTrapper(GlobalHookTypes Type):this(Type,false)
{
}
public GlobalHookTrapper(GlobalHookTypes Type, bool OnThread)
{
this.HookType = Type;
this.HookId = (int)Type;
del = ProcessMessage;
if (OnThread)
hook = SetWindowsHookEx(HookId, del, IntPtr.Zero, GetCurrentThreadId());
else
{
var hmod = IntPtr.Zero; // Marshal.GetHINSTANCE(GetType().Module);
hook = SetWindowsHookEx(HookId, del, hmod, IntPtr.Zero);
}
if (hook == IntPtr.Zero)
{
int err = Marshal.GetLastWin32Error();
if (err != 0)
OnHookFailed(err);
}
}
protected virtual void OnHookFailed(int Error)
{
throw Win32Functions.TranslateError(Error);
}
private const int HC_ACTION = 0;
[MarshalAs(UnmanagedType.FunctionPtr)]
private MessageDelegate del;
private delegate int MessageDelegate(int code, IntPtr wparam, IntPtr lparam);
private int ProcessMessage(int hookcode, IntPtr wparam, IntPtr lparam)
{
if (HC_ACTION == hookcode)
{
try
{
if (Handle(wparam, lparam)) return 1;
}
catch { }
}
return CallNextHook(hook, hookcode, wparam, lparam);
}
protected abstract bool Handle(IntPtr wparam, IntPtr lparam);
protected override sealed void OnDispose()
{
UnhookWindowsHookEx(hook);
AfterDispose();
}
protected virtual void AfterDispose()
{
}
}
public enum GlobalHookTypes
{
BeforeWindow = 4, //WH_CALLWNDPROC
AfterWindow = 12, //WH_CALLWNDPROCRET
KeyBoard = 2, //WH_KEYBOARD
KeyBoard_Global = 13, //WH_KEYBOARD_LL
Mouse = 7, //WH_MOUSE
Mouse_Global = 14, //WH_MOUSE_LL
JournalRecord = 0, //WH_JOURNALRECORD
JournalPlayback = 1, //WH_JOURNALPLAYBACK
ForeGroundIdle = 11, //WH_FOREGROUNDIDLE
SystemMessages = 6, //WH_SYSMSGFILTER
MessageQueue = 3, //WH_GETMESSAGE
ComputerBasedTraining = 5, //WH_CBT
Hardware = 8, //WH_HARDWARE
Debug = 9, //WH_DEBUG
Shell = 10, //WH_SHELL
}
public abstract class FinalizerBase : IDisposable
{
protected readonly AppDomain domain;
public FinalizerBase()
{
System.Windows.Forms.Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
domain = AppDomain.CurrentDomain;
domain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit);
domain.DomainUnload += new EventHandler(domain_DomainUnload);
}
private bool disposed;
public bool IsDisposed{get{return disposed;}}
public void Dispose()
{
if (!disposed)
{
GC.SuppressFinalize(this);
if (domain != null)
{
domain.ProcessExit -= new EventHandler(CurrentDomain_ProcessExit);
domain.DomainUnload -= new EventHandler(domain_DomainUnload);
System.Windows.Forms.Application.ApplicationExit -= new EventHandler(Application_ApplicationExit);
}
disposed = true;
OnDispose();
}
}
void Application_ApplicationExit(object sender, EventArgs e)
{
Dispose();
}
void domain_DomainUnload(object sender, EventArgs e)
{
Dispose();
}
void CurrentDomain_ProcessExit(object sender, EventArgs e)
{
Dispose();
}
protected abstract void OnDispose();
/// Destructor
~FinalizerBase()
{
Dispose();
}
}
}
I am using Visual Studio 2010 (C#) and a Windows Forms application.
I have two treeviews side by side, and I have figured out how to synchronise the scrolling using the up/down buttons on the scrollbar, but when I use the slider it does not move the other treeview. I have taken a listview example that works, but the same code does not work for treeviews.
So far I have, in the main form:
[DllImport("User32.dll")]
public static extern int SendMessage(IntPtr hWnd, uint Msg, uint wParam, uint lParam);
private void myListBox1_Scroll(ref Message m)
{
SendMessage(myListBox2.Handle, (uint)m.Msg, (uint)m.WParam, (uint)m.LParam);
}
I have created a control:
public partial class MyTreeView : TreeView
{
public MyTreeView()
{
InitializeComponent();
}
public event ScrollEventHandler Scroll;
public delegate void ScrollEventHandler(ref Message m);
private const int WM_VSCROLL = 0x115;
protected override void WndProc(ref System.Windows.Forms.Message m)
{
if (m.Msg == WM_VSCROLL)
if (Scroll != null)
{
Scroll(ref m);
}
base.WndProc(ref m);
}
}
which I add two of to the form.
I can use the same code to have a listivew control the treeview and that will work if you drag the slider, but in reverse it only works with the up down buttons.
Rather than use SendMessage and mark your DLL as unsafe you can use the GetScrollPos and SetScrollPos functions from user32.dll.
I've wrapped the code up into your MyTreeView class so it's nicely encapsulated.
You just need to call the AddLinkedTreeView method like so:
treeView1.AddLinkedTreeView(treeView2);
Here's the source for the MyTreeView class.
public partial class MyTreeView : TreeView
{
public MyTreeView() : base()
{
}
private List<MyTreeView> linkedTreeViews = new List<MyTreeView>();
/// <summary>
/// Links the specified tree view to this tree view. Whenever either treeview
/// scrolls, the other will scroll too.
/// </summary>
/// <param name="treeView">The TreeView to link.</param>
public void AddLinkedTreeView(MyTreeView treeView)
{
if (treeView == this)
throw new ArgumentException("Cannot link a TreeView to itself!", "treeView");
if (!linkedTreeViews.Contains(treeView))
{
//add the treeview to our list of linked treeviews
linkedTreeViews.Add(treeView);
//add this to the treeview's list of linked treeviews
treeView.AddLinkedTreeView(this);
//make sure the TreeView is linked to all of the other TreeViews that this TreeView is linked to
for (int i = 0; i < linkedTreeViews.Count; i++)
{
//get the linked treeview
var linkedTreeView = linkedTreeViews[i];
//link the treeviews together
if (linkedTreeView != treeView)
linkedTreeView.AddLinkedTreeView(treeView);
}
}
}
/// <summary>
/// Sets the destination's scroll positions to that of the source.
/// </summary>
/// <param name="source">The source of the scroll positions.</param>
/// <param name="dest">The destinations to set the scroll positions for.</param>
private void SetScrollPositions(MyTreeView source, MyTreeView dest)
{
//get the scroll positions of the source
int horizontal = User32.GetScrollPos(source.Handle, Orientation.Horizontal);
int vertical = User32.GetScrollPos(source.Handle, Orientation.Vertical);
//set the scroll positions of the destination
User32.SetScrollPos(dest.Handle, Orientation.Horizontal, horizontal, true);
User32.SetScrollPos(dest.Handle, Orientation.Vertical, vertical, true);
}
protected override void WndProc(ref Message m)
{
//process the message
base.WndProc(ref m);
//pass scroll messages onto any linked views
if (m.Msg == User32.WM_VSCROLL || m.Msg == User32.WM_MOUSEWHEEL)
{
foreach (var linkedTreeView in linkedTreeViews)
{
//set the scroll positions of the linked tree view
SetScrollPositions(this, linkedTreeView);
//copy the windows message
Message copy = new Message
{
HWnd = linkedTreeView.Handle,
LParam = m.LParam,
Msg = m.Msg,
Result = m.Result,
WParam = m.WParam
};
//pass the message onto the linked tree view
linkedTreeView.RecieveWndProc(ref copy);
}
}
}
/// <summary>
/// Recieves a WndProc message without passing it onto any linked treeviews. This is useful to avoid infinite loops.
/// </summary>
/// <param name="m">The windows message.</param>
private void RecieveWndProc(ref Message m)
{
base.WndProc(ref m);
}
/// <summary>
/// Imported functions from the User32.dll
/// </summary>
private class User32
{
public const int WM_VSCROLL = 0x115;
public const int WM_MOUSEWHEEL = 0x020A;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int GetScrollPos(IntPtr hWnd, System.Windows.Forms.Orientation nBar);
[DllImport("user32.dll")]
public static extern int SetScrollPos(IntPtr hWnd, System.Windows.Forms.Orientation nBar, int nPos, bool bRedraw);
}
}
Edit: Added forwarding of the WM_MOUSEWHEEL message as per MinnesotaFat's suggestion.
I have done this with TextBoxes before, but I think the solution should work for you as well:
// Get/Set Scroll positions of a control handle
private unsafe Win32.POINT GetScrollPos(System.IntPtr myHandle)
{
Win32.POINT res = new Win32.POINT();
IntPtr ptr = new IntPtr(&res);
Win32.SendMessage(myHandle, Win32.EM_GETSCROLLPOS, 0, ptr);
return res;
}
private unsafe void SetScrollPos(Win32.POINT point, System.IntPtr myHandle)
{
IntPtr ptr = new IntPtr(&point);
Win32.SendMessage(myHandle, Win32.EM_SETSCROLLPOS, 0, ptr);
}
Win32 details
public const int WM_USER = 0x400;
public const int EM_GETSCROLLPOS = (WM_USER + 221);
public const int EM_SETSCROLLPOS = (WM_USER + 222);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int x;
public int y;
}
[DllImport("user32")] public static extern int SendMessage(
HWND hwnd, int wMsg, int wParam, IntPtr lParam);
Then just attached to both of the ListView scrolled events and do something like this:
private void ListView1Scrolled(object sender, System.EventArgs e)
{
SetScrollPos(GetScrollPos(ListView1.Handle), ListView2.Handle);
}
private void ListView2Scrolled(object sender, System.EventArgs e)
{
SetScrollPos(GetScrollPos(ListView2.Handle), ListView1.Handle);
}
DoctaJonez' answer works marvellously. For completeness, if you add another condition to the if statement in the WndProc method, you can handle the mouse wheel scrolling events as well:
if (m.Msg == WM_VSCROLL || m.Msg == WM_MOUSEWHEEL)
And declare WM_MOUSEWHEEL:
private cont int WM_MOUSEWHEEL = 0x020A;
I want remove CheckBoxes where the Node.Type is 5 or 6. I use this code:
private void TvOne_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
int type = (e.Node as Node).typ;
if (type == 5 || type == 6)
{
Color backColor, foreColor;
if ((e.State & TreeNodeStates.Selected) == TreeNodeStates.Selected)
{
backColor = SystemColors.Highlight;
foreColor = SystemColors.HighlightText;
}
else if ((e.State & TreeNodeStates.Hot) == TreeNodeStates.Hot)
{
backColor = SystemColors.HotTrack;
foreColor = SystemColors.HighlightText;
}
else
{
backColor = e.Node.BackColor;
foreColor = e.Node.ForeColor;
}
using (SolidBrush brush = new SolidBrush(backColor))
{
e.Graphics.FillRectangle(brush, e.Node.Bounds);
}
TextRenderer.DrawText(e.Graphics, e.Node.Text, this.TvOne.Font,
e.Node.Bounds, foreColor, backColor);
if ((e.State & TreeNodeStates.Focused) == TreeNodeStates.Focused)
{
ControlPaint.DrawFocusRectangle(e.Graphics, e.Node.Bounds,
foreColor, backColor);
}
e.DrawDefault = false;
}
else
{
e.DrawDefault = true;
}
}
The Problem is that then the Image and the Line to the Root Node is not there.
How can Remove the Checkbox and let the Image and the Line there?
This is wrong!
In the code you've shown, you are handling the drawing yourself for all of the nodes whose type is either 5 or 6. For the rest of the types, you're simply allowing the system to draw the nodes in the default way. That's why they all have the lines as expected, but the ones you're owner-drawing do not: You forgot to draw in the lines! You see, when you say e.DrawDefault = false; it's assumed that you really do mean it. None of the regular drawing is done, including the standard lines.
You'll either need to draw in those lines yourself, or figure out how to get by without owner-drawing at all.
From the code you have now, it looks like you're trying to simulate the system's native drawing style as much as possible in your owner-draw code, so it's not clear to me what exactly you accomplish by owner-drawing in the first place. If you're just trying to keep checkboxes from showing up for type 5 and 6 nodes (which, like the lines, are simply not getting drawn because you aren't drawing them!), there's a simpler way to do that without involving owner drawing.
So, you ask, what is that simpler way to hide the checkboxes for individual nodes? Well, it turns out that the TreeView control itself actually supports this, but that functionality is not exposed in the .NET Framework. You need to P/Invoke and call the Windows API to get at it. Add the following code to your form class (make sure you've added a using declaration for System.Runtime.InteropServices):
private const int TVIF_STATE = 0x8;
private const int TVIS_STATEIMAGEMASK = 0xF000;
private const int TV_FIRST = 0x1100;
private const int TVM_SETITEM = TV_FIRST + 63;
[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Auto)]
private struct TVITEM
{
public int mask;
public IntPtr hItem;
public int state;
public int stateMask;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpszText;
public int cchTextMax;
public int iImage;
public int iSelectedImage;
public int cChildren;
public IntPtr lParam;
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam,
ref TVITEM lParam);
/// <summary>
/// Hides the checkbox for the specified node on a TreeView control.
/// </summary>
private void HideCheckBox(TreeView tvw, TreeNode node)
{
TVITEM tvi = new TVITEM();
tvi.hItem = node.Handle;
tvi.mask = TVIF_STATE;
tvi.stateMask = TVIS_STATEIMAGEMASK;
tvi.state = 0;
SendMessage(tvw.Handle, TVM_SETITEM, IntPtr.Zero, ref tvi);
}
All of the messy stuff at the top are your P/Invoke declarations. You need a handful of constants, the TVITEM structure that describes the attributes of a treeview item, and the SendMessage function. At the bottom is the function you'll actually call to do the deed (HideCheckBox). You simply pass in the TreeView control and the particular TreeNode item from which you want to remove the checkmark.
So you can remove the checkmarks from each of the child nodes to get something that looks like this:
Using TreeViewExtensions.
Usage sample:
private void MyForm_Load(object sender, EventArgs e)
{
this.treeview1.DrawMode = TreeViewDrawMode.OwnerDrawText;
this.treeview1.DrawNode += new DrawTreeNodeEventHandler(arbolDependencias_DrawNode);
}
void treeview1_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
if (e.Node.Level == 1) e.Node.HideCheckBox();
e.DrawDefault = true;
}
Here is the answer's code as an Extension method, using this you can do:
public static class TreeViewExtensions
{
private const int TVIF_STATE = 0x8;
private const int TVIS_STATEIMAGEMASK = 0xF000;
private const int TV_FIRST = 0x1100;
private const int TVM_SETITEM = TV_FIRST + 63;
[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Auto)]
private struct TVITEM
{
public int mask;
public IntPtr hItem;
public int state;
public int stateMask;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpszText;
public int cchTextMax;
public int iImage;
public int iSelectedImage;
public int cChildren;
public IntPtr lParam;
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam,
ref TVITEM lParam);
/// <summary>
/// Hides the checkbox for the specified node on a TreeView control.
/// </summary>
public static void HideCheckBox(this TreeNode node)
{
TVITEM tvi = new TVITEM();
tvi.hItem = node.Handle;
tvi.mask = TVIF_STATE;
tvi.stateMask = TVIS_STATEIMAGEMASK;
tvi.state = 0;
SendMessage(node.TreeView.Handle, TVM_SETITEM, IntPtr.Zero, ref tvi);
}
}
This is very good! The only modification I'd make is to pass only the TreeNode and not the TreeView to the HideCheckBox method. The TreeView can be retrieved from the TreeNode itself:
TreeView tvw = node.TreeView;
Basically I've set up a class to handle sending WM_SETREDRAW messages like so:
public static class DrawingLocker
{
[DllImport("user32", CharSet = CharSet.Auto)]
private extern static IntPtr SendMessage(IntPtr hWnd,
int msg, int wParam, IntPtr lParam);
private const int WM_SETREDRAW = 11; //0xB
public static void LockDrawing(IntPtr Handle)
{
SendMessage(Handle, WM_SETREDRAW, 0, IntPtr.Zero);
}
public static void UnlockDrawing(IntPtr Handle)
{
SendMessage(Handle, WM_SETREDRAW, 1, IntPtr.Zero);
}
}
I then have a Redraw method in my custom user control:
public void Redraw()
{
try
{
DrawingLocker.LockDrawing(Handle);
using (Graphics graphics = Graphics.FromHwnd(Handle))
{
//Draw Stuff
}
}
finally { DrawingLocker.UnlockDrawing(Handle); }
}
My problem is that nothing I draw where the "Draw Stuff" comment is gets drawn. What am I doing wrong?
(Redraw gets called when values that effect drawing change, including resize)
I'm not really in Windows and stuff, but judging by what MSDN says about that flag, it doesn't do what you think it does. It's used to disable redrawing controls (think a list view) while you change their contents. Disabling it inside the redraw function is probably not going to do anything.
See if you can find something related to "double buffering", because that's one technique used to avoid flicker.