I have a Form which contains several ComboBoxes.
I want one ComboBox of them to open the elements list when it gets the focus, both from keyboard and mouse.
The DroppedDown property of the ComboBox class manages the visibility of the elements list.
The event that most fits my needs is Enter, so the code I wrote is:
private void comboBox1_Enter(object sender, EventArgs e)
{
this.comboBox1.DroppedDown = true;
}
It works, but when directly clicking on the icon located on the right part of the ComboBox which does NOT have the focus, the elements list opens up and the suddenly disappears after its opening.
I've tried many ways to fix this weird behavior, checking the Focused property or using other events like DropDown or MouseClick, without getting any acceptable result.
A simple way (which doesn't force you to override a ComboBox derived Control's WndProc) is to simulate a HitTest, testing whether the MouseDown occurred on the ComboBox button area; then, set DroppedDown = true; only if it didn't.
Thus, when the Mouse is clicked on the Button, you won't cause a double effect, moving the Focus in an unexpected way (for the Control).
GetComboBoxInfo() is used to retrieve the correct bounds of the ComboBox Button, whether the current layout is (LTR or RTL).
private void comboBox1_Enter(object sender, EventArgs e)
{
var combo = sender as ComboBox;
if (!combo.DroppedDown) {
var buttonRect = GetComboBoxButtonInternal(combo.Handle);
if (!buttonRect.Contains(combo.PointToClient(Cursor.Position))) {
combo.DroppedDown = true;
Cursor = Cursors.Default;
}
}
}
Declarations for the GetComboBoxInfo() function:
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
internal static extern bool GetComboBoxInfo(IntPtr hWnd, ref COMBOBOXINFO pcbi);
[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;
}
internal static Rectangle GetComboBoxButtonInternal(IntPtr cboHandle) {
var cbInfo = new COMBOBOXINFO();
cbInfo.cbSize = Marshal.SizeOf<COMBOBOXINFO>();
GetComboBoxInfo(cboHandle, ref cbInfo);
return cbInfo.rcButton;
}
Create a new class the is inherited from ComboBox:
public class Combo : ComboBox
{
protected override void OnClick(EventArgs e)
{
if (!DroppedDown) base.OnClick(e);
}
}
And in its click call base.OnClick(e); if its not dropped down.
use this one instead of combobox. (Basically click event will be ignored if dropped down)
Use the following code on your form constructor:
this.comboBox1.GotFocus += (sender,args) => comboBox1.DroppedDown = true;
Related
I found the following solution to limit textbox to numbers. I have 20 textBoxes in my GUI is there a cleaner way than making 20 of these functions?
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
e.Handled = !char.IsDigit(e.KeyChar) && !char.IsControl(e.KeyChar);
}
My suggestion is to replace the standard TextBox Controls that need this feature, with a Custom Control that can also filter the User input when text is pasted in the Control or when the Control's Text Property is set in the Designer, using the PropertyGrid.
If you only handle the KeyPress event, you cannot prevent a bad paste.
I think it's also better to filter what is set in the Text Property, to avoid a misunderstanding.
Test this simple Custom TextBox Control: it handles direct User input and text pasted at run-time.
Setting the UserPaste Property to Disallow, pastes are ignored, while setting it to NumbersOnly (default) allows just numbers: if mixed chars are pasted in the Control, only the numbers are preserved.
To also allow the input of comma and dot, change the Regex in [^0-9,.\b].
The Text property set in the Designer is also filtered.
To replace the existing TextBox Controls with the this Custom Control, you can use the Find/Replace function (usually activated with CTRL+H) in Visual Studio:
using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Windows.Forms;
[ToolboxItem(true)]
[DesignerCategory("Code")]
public class TextBoxNumbers : TextBox
{
private Regex regex = new Regex(#"[^0-9\b]", RegexOptions.Compiled);
public TextBoxNumbers() { }
public override string Text {
get => base.Text;
set { if (!base.Text.Equals(value)) base.Text = regex.Replace(value, ""); }
}
public enum PasteAction {
NumbersOnly,
Disallow
}
[DefaultValue(PasteAction.NumbersOnly)]
public PasteAction UserPaste { get; set; }
protected override void OnKeyPress(KeyPressEventArgs e)
{
if (regex.IsMatch(e.KeyChar.ToString())) {
e.Handled = true;
}
base.OnKeyPress(e);
}
protected override void WndProc(ref Message m)
{
switch (m.Msg) {
case NativeMethods.WM_PASTE:
switch (UserPaste) {
case PasteAction.Disallow:
return;
case PasteAction.NumbersOnly:
string text = Clipboard.GetText(TextDataFormat.UnicodeText);
text = regex.Replace(text, "");
NativeMethods.SendMessage(this.Handle, NativeMethods.EM_REPLACESEL, 1, text);
return;
}
break;
}
base.WndProc(ref m);
}
}
NativeMethods class:
using System.Runtime.InteropServices;
private class NativeMethods
{
internal const int WM_PASTE = 0x0302;
internal const int EM_REPLACESEL = 0xC2;
[DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern int SendMessage(IntPtr hWnd, uint uMsg, int wParam, string lParam);
}
My simple and go-to solution would be to wirte the methode once, after that go to the desginer and select every of your texboxes that need it, click on the event-button in the propperties, scroll to the KeyPress Event and click on it once, now a dropdown-arrow should apear, select your methode there - done.
private void textBox_KeyPress(object sender, KeyPressEventArgs e)
{
e.Handled = !char.IsDigit(e.KeyChar) && !char.IsControl(e.KeyChar);
}
I have two custom WPF controls (with custom rendering logic) placed on top of each other (transparent background and stuff). At some point i want to re-draw those controls. My first idea was to call:
foreach(var control in Controls)
{
control.InvalidateVisual();
}
But that did not quite work. I mean it does force the rendering of my controls, but visual update does not happen simultaneously. The controls update one by one, which does not look nice and can lead to confusion (in cases where one control displays an updated visual, while the other still displays an old one). The documentation on InvalidateVisual method is really poor, but i guess it is an async method, which is why i am having this issue.
So the question is: is there a way to synchronize rendering of multiple controls? Apart from creating one mega control containing all the rendering logic (i would like to avoid that).
You generally shouldn't use InvalidateVisual().
To update a control which is staying the same size, you can create a DrawingGroup and put that into the visual-tree during OnRender() -- then you can update the drawing group anytime you like using DrawingGroup.Open(), and WPF will update the UI.
For your case, this would look something like:
class Control1 : UIElement {
DrawingGroup backingStore = new DrawingGroup();
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
Render(); // put content into our backingStore
drawingContext.DrawDrawing(backingStore);
}
private void Render() {
var drawingContext = backingStore.Open();
Render(drawingContext);
drawingContext.Close();
}
private void Render(DrawingContext) {
// put your existing drawing commands here.
}
}
class Control2 : UIElement {
DrawingGroup backingStore = new DrawingGroup();
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
Render(); // put content into our backingStore
drawingContext.DrawDrawing(backingStore);
}
private void Render() {
var drawingContext = backingStore.Open();
Render(drawingContext);
drawingContext.Close();
}
private void Render(DrawingContext) {
// put your existing drawing commands here.
}
}
class SomeOtherClass {
public void SomeOtherMethod() {
control1.Render();
control2.Render();
}
}
You need to suspend refreshing control until you finish invalidating all of them one way is like this :
class DrawingControl
{
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
private const int WM_SETREDRAW = 11;
public static void SuspendDrawing( Control parent )
{
SendMessage(parent.Handle, WM_SETREDRAW, false, 0);
}
public static void ResumeDrawing( Control parent )
{
SendMessage(parent.Handle, WM_SETREDRAW, true, 0);
parent.Refresh();
}
}
see here for other ways : How do I suspend painting for a control and its children?
Another approach is to use data-binding to trigger the rendering if control has some Data property that is bound to viewmodel.
public static readonly DependencyProperty DataProperty
= DependencyProperty.Register("Data", typeof(DataType), typeof(MyView),
new FrameworkPropertyMetadata(default(DataType),
FrameworkPropertyMetadataOptions.AffectsRender));
public DataType Data
{
get { return (DataType)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
Note the AffectsRender flag. Then you can re-draw all controls simultaneously by simultaneously updating bound properties:
foreach(var viewModel in ViewModels)
{
viewModel.Data = ...;
}
Is there any way of creating a tool window in WinForms that as long as the hosting form has focus, the tool window does as well? An example of this is in Paint.NET:
I'm using C# as the backend application language, under .Net 4.0.
The source code for an old version of Paint.Net is available at openpdn Fork of Paint.NET 3.36.7
I tried to extract their methods from that source code into the most concise working example I could muster:
Pinvoking class:
internal static class Win32 {
public const int WM_ACTIVATE = 0x006;
public const int WM_ACTIVATEAPP = 0x01C;
public const int WM_NCACTIVATE = 0x086;
[DllImport("user32.dll", SetLastError = false)]
internal static extern IntPtr SendMessageW(IntPtr hWnd, uint msg,
IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal extern static bool PostMessageW(IntPtr handle, uint msg,
IntPtr wParam, IntPtr lParam);
}
Base Form:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private bool ignoreNcActivate = false;
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
switch (m.Msg) {
case Win32.WM_NCACTIVATE:
if (m.WParam == IntPtr.Zero) {
if (ignoreNcActivate) {
ignoreNcActivate = false;
} else {
Win32.SendMessageW(this.Handle, Win32.WM_NCACTIVATE, new IntPtr(1), IntPtr.Zero);
}
}
break;
case Win32.WM_ACTIVATEAPP:
if (m.WParam == IntPtr.Zero) {
Win32.PostMessageW(this.Handle, Win32.WM_NCACTIVATE, IntPtr.Zero, IntPtr.Zero);
foreach (Form2 f in this.OwnedForms.OfType<Form2>()) {
f.ForceActiveBar = false;
Win32.PostMessageW(f.Handle, Win32.WM_NCACTIVATE, IntPtr.Zero, IntPtr.Zero);
}
ignoreNcActivate = true;
} else if (m.WParam == new IntPtr(1)) {
Win32.SendMessageW(this.Handle, Win32.WM_NCACTIVATE, new IntPtr(1), IntPtr.Zero);
foreach (Form2 f in this.OwnedForms.OfType<Form2>()) {
f.ForceActiveBar = true;
Win32.SendMessageW(f.Handle, Win32.WM_NCACTIVATE, new IntPtr(1), IntPtr.Zero);
}
}
break;
}
}
protected override void OnShown(EventArgs e) {
base.OnShown(e);
Form2 f = new Form2();
f.Show(this);
}
}
Always active Form2 (unless app is not active):
public partial class Form2 : Form {
internal bool ForceActiveBar { get; set; }
public Form2() {
InitializeComponent();
this.ShowInTaskbar = false;
this.ForceActiveBar = true;
}
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
if (m.Msg == Win32.WM_NCACTIVATE) {
if (this.ForceActiveBar && m.WParam == IntPtr.Zero) {
Win32.SendMessageW(this.Handle, Win32.WM_NCACTIVATE, new IntPtr(1), IntPtr.Zero);
}
}
}
}
There is no need to set TopMost to true for Form2 since it should be owned by the main form when it gets displayed. Also, Form2 is not an MDI child form.
The tool windows in Paint.NET are just that—tool windows. In Win32 terms, you achieve this by creating the window with the WS_EX_TOOLWINDOW extended window style:
The window is intended to be used as a floating toolbar. A tool window has a title bar that is shorter than a normal title bar, and the window title is drawn using a smaller font. A tool window does not appear in the taskbar or in the dialog that appears when the user presses ALT+TAB.
In WinForms, this is controlled by the FormBorderStyle property. Set it to either FormBorderStyle.FixedToolWindow or FormBorderStyle.SizableToolWindow in your form's constructor.
You also need to make sure that you specify an owner window for the tool window. Its owner should be your main form, the one for which it serves as a tool palette. You generally do this when showing the form, using the overload of the Show method that allows you to specify an owner window.
Finally, another cool effect that Paint.NET has (I think, if I remember correctly) is that the tool windows can never actually receive the focus. You can interact with them, clicking on buttons to select tools, but you can't actually set the focus to a floating palette. It always goes back to the main window. A naive attempt to emulate this behavior might be to reset the focus in one of the focus-changing notifications (e.g., the Activate event), but that's not a good idea for numerous reasons. A better solution would be to add the WS_EX_NOACTIVATE extended window style. I'm not aware of any property that exposes this functionality in WinForms, but you can set it manually during the window's creation by overriding the CreateParams property. For example:
public class MyForm : Form
{
// ... other code ...
protected override CreateParams CreateParams {
get {
const int WS_EX_NOACTIVATE = 0x08000000;
CreateParams cp = base.CreateParams;
cp.ExStyle |= WS_EX_NOACTIVATE;
return cp;
}
}
}
I don't know if Windows Forms has a built-in feature for this, but you can achieve what you want with the code below:
For main form:
private ToolForm m_toolForm;
private void MainForm_Load(object sender, EventArgs e)
{
m_toolForm = new ToolForm ();
m_toolForm.Show();
}
private void MainForm_Resize(object sender, EventArgs e)
{
switch (WindowState)
{
case FormWindowState.Minimized:
m_toolForm.Hide();
break;
case FormWindowState.Maximized:
m_toolForm.Show();
break;
}
}
For tool form:
You don't need any code, just set the property "TopMost" to true.
For those using the DevExpress RibbonForm, please use the solution in the below link to solve the focus problem. The zip file is in a comment to the solution, not in the solution itself:
http://www.devexpress.com/Support/Center/Question/Details/Q498321
I want to capture all key presses and mouse clicks in a specific user control. I need to do that to be able to create a idle detection so that I can unlock the entity if the user has not done anything in the user control for a while.
My first attempt was to PreProcessMessage to detect the windows messages, but it doesn't seem to be called? My second was to use DefWndProc, but it isn't called for those messages.
How can I get WM_KEYDOWN & WM_MOUSEUP for a user control and all of it's children?
Update
ProcessKeyPreview works to detect keys
For the keyboard aspect you could try overriding the ProcessCmdKey event within the UserControl.
UserControl.cs:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
//a key has been pressed within your user control
//invoke event or some other action
//...
return base.ProcessCmdKey(ref msg, keyData);
}
For the mouse, I imagine your UserControl implementing the IMessageFilter interface's PreFilterMessage method may be the direction to head in (although I'm not sure if that can be specific to a user control).
Edit
I had a quick look at IMessageFilter and I think I have something that may work although it does seem a bit convoluted.
Create a MessageHandler that implements IMessageFilter.
class MessageHandler : IMessageFilter
{
private int WM_LBUTTONUP = 0x0202; //left mouse up
private int WM_MBUTTONUP = 0x0208; //middle mouse up
private int WM_RBUTTONUP = 0x0205; //right mouse up
//stores the handles of the children controls (and actual user control)
List<IntPtr> windowHandles = new List<IntPtr>();
public MessageHandler(List<IntPtr> wnds)
{
windowHandles = wnds;
}
public bool PreFilterMessage(ref Message m)
{
//make sure that any click is within the user control's window or
//its child controls before considering it a click (required because IMessageFilter works application-wide)
if (windowHandles.Contains(m.HWnd) && (m.Msg == WM_LBUTTONUP || m.Msg == WM_MBUTTONUP || m.Msg == WM_RBUTTONUP))
{
Debug.WriteLine("Clicked");
}
return false;
}
}
In your user control (or new class) you can use P/Invoke to get the child windows of a given parent. From pinvoke.net
public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr i);
private static List<IntPtr> GetChildWindows(IntPtr parent, bool addParent = true)
{
List<IntPtr> result = new List<IntPtr>();
GCHandle listHandle = GCHandle.Alloc(result);
try
{
EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
}
finally
{
if (listHandle.IsAllocated)
listHandle.Free();
}
if (addParent)
result.Add(parent);
return result;
}
private static bool EnumWindow(IntPtr handle, IntPtr pointer)
{
GCHandle gch = GCHandle.FromIntPtr(pointer);
List<IntPtr> list = gch.Target as List<IntPtr>;
if (list == null)
{
throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
}
list.Add(handle);
// You can modify this to check to see if you want to cancel the operation, then return a null here
return true;
}
The GetChildWindows will return the windows we need. I modified it a bit and added an optional addParent bool which will add the user control itself to the list.
In your UserControl.cs constructor we can register the MessageFilter and pass the list of handles (or you can add this via a different form as long as you know the handle of the user control).
public MyControl()
{
InitializeComponent();
Application.AddMessageFilter(new MessageHandler(GetChildWindows(this.Handle)));
}
You should now be able to detect the three mouse button up events in your user control and any of its child windows. Don't forget to call Application.RemoveMessageFilter when your application is closed or when your UserControl is closed/disposed.
As I say, this isn't the most straightforward way, but it'll capture the clicks if the standard event handlers aren't working for you (I assume you've tried subscribing to the standard mouseclick events).
For keyboard, as already mentioned, use:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
//invoke event or some other action
return base.ProcessCmdKey(ref msg, keyData);
}
This should solve for mouse events, create new ones for each existing mouse event you need:
public new event MouseEventHandler MouseClick
{
add
{
base.MouseClick += value;
foreach (Control control in Controls)
{
control.MouseClick += value;
}
}
remove
{
base.MouseClick -= value;
foreach (Control control in Controls)
{
control.MouseClick -= value;
}
}
}
public new event MouseEventHandler MouseDoubleClick
{
add
{
base.MouseDoubleClick += value;
foreach (Control control in Controls)
{
control.MouseDoubleClick += value;
}
}
remove
{
base.MouseDoubleClick -= value;
foreach (Control control in Controls)
{
control.MouseDoubleClick -= value;
}
}
}
public new event EventHandler DoubleClick
{
add
{
base.DoubleClick += value;
foreach (Control control in Controls)
{
control.DoubleClick += value;
}
}
remove
{
base.DoubleClick -= value;
foreach (Control control in Controls)
{
control.DoubleClick -= value;
}
}
}
I would catch the following events:
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.click.aspx
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.textchanged.aspx And any click/changed events for subcontrols. Have all the event handlers call the same common function that saves a timestamp of the last action.
Click += ClickHandler;
TextChanged += ChangedHandler;
foreach(var control in Controls)
{
control.Click += ClickHandler;
control.TextChanged += ChangedHandler;
}
(Don't forget to unsubscribe to these controls later, and if you have controls dynamically getting added and removed, handle the subscribe/unsubscribe appropriately.)
If catching them no matter which application has the focus isn't a problem, you could always use a Gobal Keyboard/Mouse hook. It is a bit too complicated to explain in detail here but you can always refer to my question where I did something similar.
The functionality I'm after is a cross between a check list box and a list box in multi selection mode.
For list box items A and B
A then B results in a single selection that moves from A to B.
A then control-click B results in a multi-selection of A and B.
(What I want is):
A then A results in an A toggling on and off.
I thought this would be easy but I can't figure it out. Maybe I'm missing something obvious or maybe I'm thinking the wrong way and nobody really wants a listbox whos items toggle on/off.
If you set SelectionMode to MultiSimple this gets you the control-click multi-selection and the toggling on and off.
To get the moving selection to work you could handle the SelectedIndexChanged event and add some logic to de-select the other items if control isn't pressed. If I have more time later I could try to create some basic code for it, but this should be somewhere to start.
You already have the behavior you want if you set the ListBox.SelectionMode to MultiExtended and hold down control when making a selection.
If I understood correctly your problem, you want a ListBox with SelectionMode.One that can toggle selections with CTLR modifier similarly to SelectionMode.MultiSimple and SelectionMode.MultiExtented. Below my answer, just set ToggleSingleSelection to true. Also as a bonus it provides items click events that fires also when clicking already selected items.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Common
{
public class ListBoxEx : ListBox
{
public event ItemClickedEventHandler ItemClick;
public event ItemClickedEventHandler ItemDoubleClick;
/// <summary>
/// Toggle selections when list has SelectionMode.One
/// </summary>
public bool ToggleSingleSelection { get; set; }
int _PreSelectedIndex = -1;
int _PrevClickedItem = -1;
protected override void OnSelectedIndexChanged(EventArgs e)
{
base.OnSelectedIndexChanged(e);
}
protected override void WndProc(ref Message m)
{
const int WM_LBUTTONDOWN = 0x201;
switch (m.Msg)
{
case WM_LBUTTONDOWN:
// NOTE: Unfortunately SelectedIndex is already setted before OnMouseDown,
// so we must intercept mouse click even before to compare
_PreSelectedIndex = SelectedIndex;
break;
}
base.WndProc(ref m);
}
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
// Reset clicked event, also for double click
_PrevClickedItem = -1;
int selectedIndex = SelectedIndex;
if (selectedIndex != -1)
{
Rectangle selectedItemRectangle = GetItemRectangle(selectedIndex);
if (selectedItemRectangle.Contains(e.Location))
{
_PrevClickedItem = selectedIndex;
// Check when to toggle selection
if (SelectionMode == SelectionMode.One && ToggleSingleSelection && ModifierKeys.HasFlag(Keys.Control)
&& _PreSelectedIndex != -1 && selectedIndex == _PreSelectedIndex)
{
SelectedIndex = -1;
}
if (_PrevClickedItem != -1)
OnItemClick(new ItemClickedEventArgs() { ItemIndex = _PrevClickedItem });
}
}
}
protected override void OnMouseDoubleClick(MouseEventArgs e)
{
base.OnMouseDoubleClick(e);
if (_PrevClickedItem != -1)
OnItemDoubleClick(new ItemClickedEventArgs() { ItemIndex = _PrevClickedItem });
}
protected virtual void OnItemDoubleClick(ItemClickedEventArgs args)
{
ItemDoubleClick?.Invoke(this, args);
}
protected virtual void OnItemClick(ItemClickedEventArgs args)
{
ItemClick?.Invoke(this, args);
}
}
public class ItemClickedEventArgs : EventArgs
{
public int ItemIndex { get; set; }
}
public delegate void ItemClickedEventHandler(Control sender, ItemClickedEventArgs args);
}