How to disable read operation from Combobox in WinForms? - c#

I know the command in WPF but what is its equivalent in WinForms?
cboclient.IsHitTestVisible = false;
cboclient.Focusable = false;
Using this command the combo-box is not disabled but the user can't open it for reading the data. How I can accomplish this in WinForms? thanks
Details: I have 3 combobox on my form when the form initially loads only the third combobox can not be opened for reading data. When the user selects a value in the first two combobox then based on those two values the third combobox is enabled to display data from DB.
Note: Here I don't want to disable the third combobox. Because it will give the user the a false expression.

You can catch the message WM_MOUSEACTIVATE and discard it to prevent user from focusing the combobox by mouse and also prevent hittesting. Catch the message WM_SETFOCUS to prevent user from focusing the combobox by keyboard. Try this code:
public class ComboBoxEx : ComboBox
{
public ComboBoxEx(){
IsHitTestVisible = true;
}
public bool IsHitTestVisible { get; set; }
public bool ReadOnly { get; set; }
protected override void WndProc(ref Message m)
{
if (!IsHitTestVisible)
{
if (m.Msg == 0x21)//WM_MOUSEACTIVATE = 0x21
{
m.Result = (IntPtr)4;//no activation and discard mouse message
return;
}
//WM_MOUSEMOVE = 0x200, WM_LBUTTONUP = 0x202
if (m.Msg == 0x200 || m.Msg == 0x202) return;
}
//WM_SETFOCUS = 0x7
if (ReadOnly && m.Msg == 0x7) return;
base.WndProc(ref m);
}
//Discard key messages
public override bool PreProcessMessage(ref Message msg)
{
if (ReadOnly) return true;
return base.PreProcessMessage(ref msg);
}
}
//Usage
comboBoxEx1.ReadOnly = true;
comboBoxEx1.IsHitTestVisible = false;

You can use if statement on OnSelectionChangedSelectionChanged event.
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//here your if statement
}

You can use following code:
cboclient.DropDownStyle = ComboBoxStyle.DropDownList;
cboclient.DropDownHeight = 1;
cboclient.DropDownWidth = 1;
cboclient.TabStop = false;
for displaying combobox as a readonly one you can use:
cboclient.FlatStyle = FlatStyle.Popup;
or
cboclient.FlatStyle = FlatStyle.Flat;

Related

Handle navigation keys in TextBox inside DataGridView

We have a DataGridView with data in a form. To enable quick search, we added TextBox to DataGridView.Controls and highlight cells which contain text from TextBox.
However, there is an issue. DataGridView consumes the Left arrow ←, Right arrow →, Home and End (with or without Shift) keys even if the cursor is in TextBox, and the user cannot change the caret position or select text from the keyboard.
TextBox generates a PreviewKeyDown event and nothing more happens.
Simplified code:
public partial class TestForm : Form
{
public TestForm()
{
InitializeComponent();
Width = 400;
Height = 400;
var txt = new TextBox { Dock = DockStyle.Bottom, BackColor = Color.Khaki };
var dgv = new DataGridView
{
Dock = DockStyle.Fill,
ColumnCount = 3,
RowCount = 5
};
dgv.Controls.Add(txt);
Controls.Add(dgv);
dgv.PreviewKeyDown += DgvOnPreviewKeyDown;
dgv.KeyDown += DgvOnKeyDown;
txt.PreviewKeyDown += TxtOnPreviewKeyDown;
txt.KeyDown += TxtOnKeyDown;
}
private void DgvOnPreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{
Debug.WriteLine(String.Format("Dgv Key Preview {0}", e.KeyCode));
e.IsInputKey = true;
}
private void DgvOnKeyDown(object sender, KeyEventArgs e)
{
Debug.WriteLine(String.Format("Dgv Key {0}", e.KeyCode));
}
private void TxtOnPreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{
Debug.WriteLine(String.Format("Txt Key Preview {0}", e.KeyCode));
}
private void TxtOnKeyDown(object sender, KeyEventArgs e)
{
Debug.WriteLine(String.Format("Txt Key {0}", e.KeyCode));
}
}
Type 123 in TextBox and then try the Right arrow, Left arrow, End, or Home. DataGridView change the selected cell, but the TextBox caret doesn't move.
TextBox works just fine if not inside a DataGridView (no problem at all when using the same method adding it into TreeView for example). TextBox acts similar to the Quick search Panel in the browser and has to be on top of the DataGridView. Adding a TextBox to a Form (or to be more specific, to a DataGridView parent) creates its own set of issues (tracking Location, Size, Visibility, ...) and is not acceptable.
What can be done to make sure that TextBox receive those keys and change the caret position or select text?
TextBox works just fine if not inside DataGridView (no problem at all when using the same method adding it into TreeView for example)
Apparently the problem is in DataGridView. It's because DataGridView overrides the Control.ProcessKeyPreview method:
This method is called by a child control when the child control receives a keyboard message. The child control calls this method before generating any keyboard events for the message. If this method returns true, the child control considers the message processed and does not generate any keyboard events.
The DataGridView implementation does just that - it maintains zero or one child controls internally (EditingControl), and when there is no such control active, it handles many keys (navigation, tab, enter, escape, etc.) by returning true, thus preventing the child TextBox keyboard events generation. The return value is controlled by the ProcessDataGridViewKey method.
Since the method is virtual, you can replace the DataGridView with a custom derived class which overrides the aforementioned method and prevents the undesired behavior when neither the view nor the view active editor (if any) has the keyboard focus.
Something like this:
public class CustomDataGridView : DataGridView
{
bool SuppressDataGridViewKeyProcessing => ContainsFocus && !Focused &&
(EditingControl == null || !EditingControl.ContainsFocus);
protected override bool ProcessDataGridViewKey(KeyEventArgs e)
{
if (SuppressDataGridViewKeyProcessing) return false;
return base.ProcessDataGridViewKey(e);
}
}
The above is just the half of the story and solves the cursor navigation and selection keys issue. However DataGridView intercepts another key message preprocessing infrastructure method - Control.ProcessDialogKey and handles Tab, Esc, Return, etc. keys there. So in order to prevent that, the method has to be overridden as well and redirected to the parent of the data grid view. The later needs a little reflection trickery to call a protected method, but using one time compiled delegate at least avoids the performance hit.
With that addition, the final custom class would be like this:
public class CustomDataGridView : DataGridView
{
bool SuppressDataGridViewKeyProcessing => ContainsFocus && !Focused &&
(EditingControl == null || !EditingControl.ContainsFocus);
protected override bool ProcessDataGridViewKey(KeyEventArgs e)
{
if (SuppressDataGridViewKeyProcessing) return false;
return base.ProcessDataGridViewKey(e);
}
protected override bool ProcessDialogKey(Keys keyData)
{
if (SuppressDataGridViewKeyProcessing)
{
if (Parent != null) return DefaultProcessDialogKey(Parent, keyData);
return false;
}
return base.ProcessDialogKey(keyData);
}
static readonly Func<Control, Keys, bool> DefaultProcessDialogKey =
(Func<Control, Keys, bool>)Delegate.CreateDelegate(typeof(Func<Control, Keys, bool>),
typeof(Control).GetMethod(nameof(ProcessDialogKey), BindingFlags.NonPublic | BindingFlags.Instance));
}
You can try this.
I created my own textbox and overrode method ProcessKeyMessage.
public class MyTextBox : TextBox
{
private const int WM_KEYDOWN = 0x0100;
private const int WM_SYSKEYDOWN = 0x0104;
protected override bool ProcessKeyMessage(ref Message m)
{
if (m.Msg != WM_SYSKEYDOWN && m.Msg != WM_KEYDOWN)
{
return base.ProcessKeyMessage(ref m);
}
Keys keyData = (Keys)((int)m.WParam);
switch (keyData)
{
case Keys.Left:
case Keys.Right:
case Keys.Home:
case Keys.End:
case Keys.ShiftKey:
return base.ProcessKeyEventArgs(ref m);
default:
return base.ProcessKeyMessage(ref m);
}
}
}
And then you can call:
var txt = new MyTextBox { Dock = DockStyle.Bottom, BackColor = Color.Khaki };
Try to just add the TextBox to the main form instead of the DataGridView:
Controls.Add(txt);
Controls.Add(dgv);
txt.PreviewKeyDown += DgvOnPreviewKeyDown;
txt.KeyDown += DgvOnKeyDown;
txt.PreviewKeyDown += TxtOnPreviewKeyDown;
txt.KeyDown += TxtOnKeyDown;
It sounds a bit like an exercise in futility.
It may be easier to encapsulate the behavior of both the TextBox and DataGridView controls by placing them into a UserControl together with a little code to handle events.
Here is a partial solution to the issue. TextBox still doesn't receive navigation keys input natively, but I reproduced a normal caret and selection behavior.
PreviewKeyDownEventArgs contains information about the pressed key and modifiers (Shift). For each key combination I set a new SelectionStart and SelectionLength for the TextBox.
private void TxtOnPreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{
TextBox txt = (TextBox)sender;
if (e.KeyCode == Keys.Home)
{
int idx = txt.SelectionStart;
txt.SelectionStart = 0;
txt.SelectionLength = e.Shift ? idx : 0;
}
else if (e.KeyCode == Keys.End)
{
int idx = txt.SelectionStart;
if (e.Shift)
txt.SelectionLength = txt.TextLength - idx;
else
{
txt.SelectionStart = txt.TextLength;
txt.SelectionLength = 0;
}
}
else if (e.KeyCode == Keys.Left)
{
if (e.Shift)
{
if (txt.SelectionStart > 0)
{
txt.SelectionStart--;
txt.SelectionLength++;
}
}
else
{
txt.SelectionStart = Math.Max(0, txt.SelectionStart - 1);
txt.SelectionLength = 0;
}
}
else if (e.KeyCode == Keys.Right)
{
if (e.Shift)
txt.SelectionLength++;
else
{
txt.SelectionStart = Math.Min(txt.TextLength, txt.SelectionStart + 1);
txt.SelectionLength = 0;
}
}
}

ListView ContextMenuStrip for column headers

I display a different ContextMenuStrip when I right click the ListView column header, and another inside ListView.
class ListViewExx : ListView
{
public ContextMenuStrip HeaderContextMenu { get; set; }
int contextMenuSet = 0;
protected override void WndProc(ref System.Windows.Forms.Message m)
{
base.WndProc(ref m);
switch(m.Msg)
{
case 0x210: //WM_PARENTNOTIFY
contextMenuSet = 1;
break;
case 0x21: //WM_MOUSEACTIVATE
contextMenuSet++;
break;
case 0x7b: //WM_CONTEXTMENU
if(contextMenuSet == 2 && HeaderContextMenu != null)
HeaderContextMenu.Show(Control.MousePosition);
break;
}
}
}
This works very well. The problem is the FIRST TIME I right click inside the ListView - the headers contextMenuStrip is shown.
Relying on the activation state is too hacky. It is far simpler, the WM_CONTEXTMENU message passes the handle of the window that generated the message. So you can simply compare it to the handle of the listview. If it doesn't match then you know it was the header control:
protected override void WndProc(ref System.Windows.Forms.Message m)
{
base.WndProc(ref m);
if (m.Msg == 0x7b) { //WM_CONTEXTMENU
if (m.WParam != this.Handle) HeaderContextMenu.Show(Control.MousePosition);
}
}
Technically you should use LVM_GETHEADER but this should work just fine.
I've tried finding a clean way to get Column Header Rectangle of a ListView to check if the Point at which user right-clicks is in a Column Header or not. However, I've just found that the Column Header Rectangle of a ListView seems to be revealed only in a DrawColumnHeader event handler. This solution is all what I can think of to help you out:
public class CustomListView : ListView
{
//This contains the Column Index and its corresponding Rectangle in screen coordinates.
Dictionary<int, Rectangle> columns = new Dictionary<int, Rectangle>();
public CustomListView()
{
OwnerDraw = true;//This will help the OnDrawColumnHeader be called.
}
protected override void OnDrawItem(DrawListViewItemEventArgs e)
{
e.DrawDefault = true;
base.OnDrawItem(e);
}
protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e)
{
e.DrawDefault = true;
base.OnDrawSubItem(e);
}
protected override void OnDrawColumnHeader(DrawListViewColumnHeaderEventArgs e)
{
columns[e.ColumnIndex] = RectangleToScreen(e.Bounds);
e.DrawDefault = true;
base.OnDrawColumnHeader(e);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x7b)//WM_CONTEXTMENU
{
int lp = m.LParam.ToInt32();
int x = ((lp << 16) >> 16);
int y = lp >> 16;
foreach (KeyValuePair<int, Rectangle> p in columns)
{
if (p.Value.Contains(new Point(x, y)))
{
//MessageBox.Show(Columns[p.Key].Text); <-- Try this to test if you want.
//Show your HeaderContextMenu corresponding to a Column here.
break;
}
}
}
base.WndProc(ref m);
}
}

unselectable node in TreeView

I have TreeView control on winform. I desire to make several nodes unselectable. How can I achive this.
There is only one idea in my mind - custom drawn nodes, but may be more easier way exists? Please advice me
I have already try such code in BeforeSelect event handler:
private void treeViewServers_BeforeSelect(object sender, TreeViewCancelEventArgs e)
{
if (e.Node.Parent != null)
{
e.Cancel = true;
}
}
But effect it gained is not appropriate. Node temporary get selection when I am holding left mouse button on it.
Thanks in advance!
You could completely disable mouse events in case you click on a not-selectable node.
To do this, you have to override TreeView a shown in the following code
public class MyTreeView : TreeView
{
int WM_LBUTTONDOWN = 0x0201; //513
int WM_LBUTTONUP = 0x0202; //514
int WM_LBUTTONDBLCLK = 0x0203; //515
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_LBUTTONDOWN ||
m.Msg == WM_LBUTTONUP ||
m.Msg == WM_LBUTTONDBLCLK)
{
//Get cursor position(in client coordinates)
Int16 x = (Int16)m.LParam;
Int16 y = (Int16)((int)m.LParam >> 16);
// get infos about the location that will be clicked
var info = this.HitTest(x, y);
// if the location is a node
if (info.Node != null)
{
// if is not a root disable any click event
if(info.Node.Parent != null)
return;//Dont dispatch message
}
}
//Dispatch as usual
base.WndProc(ref m);
}
}

how to disable copy, Paste and delete features on a textbox using C#

Can anybody please suggest how to handle Cut,Copy and Paste events on a Text Box in WinForms using C#?
In WinForms, the easiest way to disable cut, copy and paste features on a textbox is to set the ShortcutsEnabled property to false.
You'd have to subclass the textbox and then override the WndProc method to intercept the windows messages before the control does.
Here's an example that illustrates a TextBox that intercepts the WM_PASTE message.
And for reference, here's the definition of the message constants:
WM_PASTE
WM_COPY
WM_CUT
You'd simply ignore the inbound message, like so:
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_PASTE || m.Msg == WM_COPY || m.Msg == WM_CUT)
{
// ignore input if it was from a keyboard shortcut
// or a Menu command
}
else
{
// handle the windows message normally
base.WndProc(ref m);
}
}
Suppose you have a TextBox named textbox1. It sounds like you want to disable the cut, copy and paste functionality of a TextBox.
Try this quick and dirty proof of concept snippet:
private void Form1_Load(object sender, EventArgs e)
{
ContextMenu _blankContextMenu = new ContextMenu();
textBox1.ContextMenu = _blankContextMenu;
}
private const Keys CopyKeys = Keys.Control | Keys.C;
private const Keys PasteKeys = Keys.Control | Keys.V;
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if ((keyData == CopyKeys) || (keyData == PasteKeys))
{
return true;
}
else
{
return base.ProcessCmdKey(ref msg, keyData);
}
}
To prevent users to copy/paste using the keyboard set ShortcutsEnabled property to false.
To prevent users to copy/paste from the context menu set ContextMenu property to new ContextMenu().
if (copyPasteEnabled) {
textBox1.ShortcutsEnabled = true;
textBox1.ContextMenu = null;
} else {
textBox1.ShortcutsEnabled = false;
textBox1.ContextMenu = new ContextMenu();
}
private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
e.SuppressKeyPress = true;
}
if (e.Control == true)
{
switch (e.KeyCode)
{
case Keys.C:
case Keys.P:
case Keys.X:
e.Handled = true;
textBox1.SelectionLength = 0;
break;
}
}
}
private void textBox1_Enter(object sender, EventArgs e)
{
System.Windows.Forms.Clipboard.Clear();
}
int cusorposition = m_TextBox1.SelectionStart;
if (TextBox1.Text[0] == ' ')
{
//Trim Spaces at beginning.
m_TextBox1.Text = m_TextBox1.Text.TrimStart(' ');
m_TextBox1.Text = m_TextBox1.Text.TrimEnd(' ');
m_TextBox1.SelectionStart = cusorposition ;
}
Hi I found a way how to get the current cursor position instead of handling cut, copy and Paste event in a text box named TextBox1.Here in the above I am keeping the backup of current Cursor Position and after trimming the extra spaces from the starting and from end position I am reassigning the current cursor position.
Thanks to all who helped me to fix this problem.

How to override the minimize control?

I want to override the minimize control to instead of sending the window to the taskbar it would do what ever I write it to do.
Basicly this is what I wanted my new minimized and restored effects to be:
private void ChangeForm(object sender, EventArgs e)
{
if (this.WindowState == FormWindowState.Minimized)
{
this.Height = 80;
iDebug.Visible = false;
mainMenu.Visible = false;
}
else
{
this.Height = 359;
iDebug.Visible = true;
mainMenu.Visible = true;
}
}
I have tried to fire an Event on the Resize to do this but without success
this.Resize += new EventHandler(ChangeForm);
Cancel A WinForm Minimize?
Just tested this and it will make the form 100 pixels shorter when minimize is clicked without flicker.
private const int WM_SYSCOMMAND = 0x0112;
private const int SC_MINIMIZE = 0xf020;
protected override void WndProc(ref Message m) {
if (m.Msg == WM_SYSCOMMAND) {
if (m.WParam.ToInt32() == SC_MINIMIZE) {
m.Result = IntPtr.Zero;
Height -= 100;
return;
}
}
base.WndProc(ref m);
}
The Minimize command has a very well defined meaning to a user, it shouldn't be messed with. Winforms accordingly doesn't have an event for it. But not a real problem, you can detect any Windows message by overriding WndProc(). Like tihs:
private void OnMinimize() {
this.Close(); // Do your stuff
}
protected override void WndProc(ref Message m) {
// Trap WM_SYSCOMMAND, SC_MINIMIZE
if (m.Msg == 0x112 && m.WParam.ToInt32() == 0xf020) {
OnMinimize();
return; // NOTE: delete if you still want the default behavior
}
base.WndProc(ref m);
}

Categories

Resources