C# Send keystroke to DataGridViewCell - c#

I have a DateTimePicker cell in my DataGridView. I'd like to be able to enter edit mode and drop the calendar when a button is clicked. I'm able to do the first part without difficulty but the second isn't working. If I have a standalone DateTimePicker the SendKeys call does work as expected.
//Select the cell and enter edit mode - works
myDGV.CurrentCell = myDGV[calColumn.Index, e.RowIndex];
myDGV.BeginEdit(true);
//Send an ALt-Down keystroke to drop the calendar - doesn't work
SendKeys.SendWait("%{DOWN}");
From debugging I believe that the problem is that the keystroke is being sent to the DGV and not the specific cell that I'm trying to edit. The reason I think is is that I've put code to log keys received by the grids KeyPress and KeyDown events. They log my arrowing around the grid and the keys sent by SendKeys, but not those from when I'm editing a cell by typing in it.

Please see my answer on C# Winforms DataGridView Time Column. I believe it will fit your needs perfectly. You can also use it for a column that has a ComboBox.

I recently revisited this issue because the implementation provided by 0A0D didn't always play nicely with keyboard navigation of the grid (arrows/tab). At times it was possible to bypass the DateTimePicker and enter text into the DataGridViewTextBoxCell. This caused my validation logic to freak out; and after failing to find a way to prevent the slip around from happening I decided to try and get the custom column working again.
The fix turned out to be very simple. I created an extended DateTimePicker with a method to send the keystroke needed to display the calendar.
/// <summary>
/// Extended DateTimePicker with a method to programmatically display the calendar.
/// </summary>
class DateTimePickerEx : DateTimePicker
{
[DllImport("user32.dll")]
private static extern bool PostMessage(
IntPtr hWnd, // handle to destination window
Int32 msg, // message
Int32 wParam, // first message parameter
Int32 lParam // second message parameter
);
const Int32 WM_LBUTTONDOWN = 0x0201;
/// <summary>
/// Displays the calendar input control.
/// </summary>
public void ShowCalendar()
{
Int32 x = Width - 10;
Int32 y = Height / 2;
Int32 lParam = x + y * 0x00010000;
PostMessage(Handle, WM_LBUTTONDOWN, 1, lParam);
}
}
I then modified the MSDN DateTime column example to have CalendarEditingControl inherit from DateTimePickerEx.
Then in the form hosting the DataGridView I used the EditingControl property to call the ShowCalendar() method.
DateTimePickerEx dtp = myDataGridView.EditingControl as DateTimePickerEx;
if (dtp != null)
dtp.ShowCalendar();

Related

In a Winforms RichTextBox control, how can I make the space BELOW a last-line link NOT clickable?

In a Windows forms C# application, I have a number of RichTextBox controls that display a link as the last line of the text box, with no line break after.
The issue is that ALL of the white space that is physically below the link will be a clickable link. I understand that empty white space below text generally serves as "part" of that line in windows--for example, put your cursor just below this post, and click and drag--you will select the last line. But generally this does not include clickable links. Try it with the title of this post--you can select the title, but your cursor is not the clickable "hand" until you are actually directly over the title.
I could get around this by changing my data to always include a trailing line break, or modify the point where I'm setting the text of the box to always add one. But both of those seem messy. Is there no way to make a RichTextBox's links act more like links in a web browser?
I can reproduce this behavior by creating a sample WinForms application, dropping in a RichTextBox, and using the designer to set the text to "http://www.google.com" Anywhere BELOW the link will show the hand cursor.
I'm using Windows 7 / VS2010 / C# / .net Framework 4.0
Thanks for the advice.
Anywhere BELOW the link will show the hand cursor.
You need to put a line break to see the text cursor below the link not the hand cursor. Its by design.
I could get around this by changing my data to always include a
trailing line break, or modify the point where I'm setting the text of
the box to always add one. But both of those seem messy. Is there no
way to make a RichTextBox's links act more like links in a web
browser?
No. Put a line break after. Or set the RichTexbox DetectUrls property to false. Or as Hans mentioned use a Web Browser. Or use a 3rd party or open source RichTextBox control.
It would be good if the Cursor change event fired when hovering over a hyperlink but it doesn't :(
It would be good if the Cursor change event fired when hovering over a hyperlink but it doesn't :(
Jeremy's comment gave me an idea: surely the native RichTextBox control does receive some type of notification when the user hovers over a hyperlink, it apparently just is not exposed by the WinForms wrapper class.
A bit of research confirms my supposition. A RichTextBox control that is set to detect hyperlinks sends a EN_LINK notification to its parent through the WM_NOTIFY message. By processing these EN_LINK notifications, then, you can override its behavior when a hyperlink is hovered.
The WinForms wrapper handles all of this in private code and does not allow the client to have any direct control over this behavior. But by overriding the parent window's (i.e., your form) window procedure (WndProc), you can intercept WM_NOTIFY messages manually and watch for EN_LINK notifications.
It takes a bit of code, but it works. For example, if you suppress the WM_SETCURSOR message for all EN_LINK notifications, you won't see the hand cursor at all.
[StructLayout(LayoutKind.Sequential)]
struct CHARRANGE
{
public int cpMin;
public int cpMax;
};
[StructLayout(LayoutKind.Sequential)]
struct NMHDR
{
public IntPtr hwndFrom;
public IntPtr idFrom;
public int code;
};
[StructLayout(LayoutKind.Sequential)]
struct ENLINK
{
public NMHDR nmhdr;
public int msg;
public IntPtr wParam;
public IntPtr lParam;
public CHARRANGE chrg;
};
public class MyForm : Form
{
// ... other code ...
protected override void WndProc(ref Message m)
{
const int WM_NOTIFY = 0x004E;
const int EN_LINK = 0x070B;
const int WM_SETCURSOR = 0x0020;
if (m.Msg == WM_NOTIFY)
{
NMHDR nmhdr = (NMHDR)m.GetLParam(typeof(NMHDR));
if (nmhdr.code == EN_LINK)
{
ENLINK enlink = (ENLINK)m.GetLParam(typeof(ENLINK));
if (enlink.msg == WM_SETCURSOR)
{
// Set the result to indicate this message has been handled,
// and return without calling the default window procedure.
m.Result = (IntPtr)1;
return;
}
}
}
base.WndProc(ref m);
}
}
Unfortunately, that's the easy part. Now comes the ugly hack where we work around the default behavior of the control that you describe, where it treats the remainder of the control's height as part of the last line if the last line is a hyperlink.
To do this, we need to get the current position of the mouse pointer and compare it against the position of the hyperlink text that the control has detected. If the mouse pointer is within the hyperlinked line, we allow the default behavior and show the hand cursor. Otherwise, we suppress the hand cursor. See the commented code below for a potentially better explanation of the process (obviously, rtb is your RichTextBox control):
protected override void WndProc(ref Message m)
{
const int WM_NOTIFY = 0x004E;
const int EN_LINK = 0x070B;
const int WM_SETCURSOR = 0x0020;
if (m.Msg == WM_NOTIFY)
{
NMHDR nmhdr = (NMHDR)m.GetLParam(typeof(NMHDR));
if (nmhdr.code == EN_LINK)
{
ENLINK enlink = (ENLINK)m.GetLParam(typeof(ENLINK));
if (enlink.msg == WM_SETCURSOR)
{
// Get the position of the last line of text in the RichTextBox.
Point ptLastLine = rtb.GetPositionFromCharIndex(rtb.TextLength);
// That point was in client coordinates, so convert it to
// screen coordinates so that we can match it against the
// position of the mouse pointer.
ptLastLine = rtb.PointToScreen(ptLastLine);
// Determine the height of a line of text in the RichTextBox.
//
// For this simple demo, it doesn't matter which line we use for
// this since they all use the same size and style. However, you
// cannot generally rely on this being the case.
Size szTextLine = TextRenderer.MeasureText(rtb.Lines[0], rtb.Font);
// Then add that text height to the vertical position of the
// last line of text in the RichTextBox.
ptLastLine.Y += szTextLine.Height;
// Now that we know the maximum height of all lines of text in the
// RichTextBox, we can compare that to the pointer position.
if (Cursor.Position.Y > ptLastLine.Y)
{
// If the mouse pointer is beyond the last line of text,
// do not treat it as a hyperlink.
m.Result = (IntPtr)1;
return;
}
}
}
}
base.WndProc(ref m);
}
Tested and working… But did I mention that this is an ugly hack? Treat it more like a proof of concept. I certainly don't recommend using it in production code. I'm in fairly strong agreement with Hans and Jeremy that you should either take the simpler approach of adding a line break, or use a more appropriate control designed to display hyperlinks.

TextBox with vertical scrollbar on the left side

I'm developing a Windows Forms application in C#, which has a multiline TextBox control on a form.
Due to specific (irrelevant) reasons, this TextBox needs a vertical scrollbar on the left side of the TextBox control. I've off course searched for a solution, but I couldn't find any... so my questions are:
1) Is there a way to make the automatic vertical scrollbar of a TextBox control (or a usercontrol derived from TextBox or TextBoxBase) appear on the left instead of the right? This is the preferred method, since all scolling is then still handled by the control. Since chancing the RightToLeft property for such a TextBox actually moves the scrollbar to the left, I feel there must be a hack to be exploited here.
or
2) Is there a message that I can intercept with my IMessageFilter implementation when the TextBox is scrolled, even though it doesn't have scrollbars? I.e. a user can scroll using the arrow keys and the textbox will move lines up and down, but I can't find any messages fired when that occurs.
Maybe another idea of how to accomplish this?
Edit to add: The text needs to be aligned to the right horizontally! Otherwise I would have solved it already.
New edit as of 11/03/2014: Okay, after BenVlodgi's comment I started having doubts about my own sanity. So I created a test project and now I remember why setting RightToLeft to Yes was not working.
The image below shows a regular TextBox on the left with that setting. The scrollbar is on the left and the text on the right, but the text is not shown properly. The period at the end of the sentence is moved in front of the sentence.
The second TextBox control is the one suggested in LarsTech's answer, which functions correctly and does not move any punctuation.
Therefore, I accept and reward the bounty to LarsTech's answer.
I took some of the example code from Rachel Gallen's link and made this version of the TextBox:
public class TextBoxWithScrollLeft : TextBox {
private const int GWL_EXSTYLE = -20;
private const int WS_EX_LEFTSCROLLBAR = 16384;
[DllImport("user32", CharSet = CharSet.Auto)]
public extern static int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32")]
public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
int style = GetWindowLong(Handle, GWL_EXSTYLE);
style = style | WS_EX_LEFTSCROLLBAR;
SetWindowLong(Handle, GWL_EXSTYLE, style);
}
}
I've never done it before, but the results seem to have worked:
By setting the RightToLeft property true. But it said the content would also be from right to left, so I don't know if that would solve your problem...But that's a way to set the scrollbar on the left hand side.
http://bytes.com/topic/c-sharp/answers/255138-scrollbar-position

Keydown event being swallowed/not actioned in DataGridView control

I have a problem with some code I developed based on the article DataGridView keydown event not working in C#.
I wanted to allow the user to add a row to a Dataviewgrid control, but found that by enabling AllowUserToAddRows this caused an additional row to be shown as soon as the first character was typed into the new cell in the new row. This would have been confusing to my poor user, so to prevent this, I used the code from above article to immediately disable AllowUserToAddRows at every keystroke (although would have preferred to only do it after the first char was typed). However, this seems to swallow the 1st char typed, i,.e. it is not passed onto the base class for processing. Here's the full code:
public sealed class XDataGridView : DataGridView
{
private bool _singleUpdateOnly = true;
public bool SingleUpdateOnly
{
get { return _singleUpdateOnly; }
set { _singleUpdateOnly = value; }
}
[Description("Disallows user adding rows as soon as a key is struck to ensure no blank row at bottom of grid")]
protected override bool ProcessCmdKey(ref System.Windows.Forms.Message msg, System.Windows.Forms.Keys keyData)
{
if (SingleUpdateOnly)
{
AllowUserToAddRows = false;
}
return base.ProcessCmdKey(ref msg, keyData);
}
}
Why is the 1st char I type swallowed? How can I prevent this from happening? Is there a better way than what I have coded?
I've had very little response to this question on this forum and others. It must be a very difficult problem, or I'm simply expecting too much from the DataGridView control.
After many hours of trial and error, I have settled on a solution which does not require subclassing. The solution is not exactly what I wanted, i.e. to prevent another another row from being appended to the bottom of the grid once the user begins typing into the newly added row, but it comes close. I use the CellLeave event to turn off AllowUserToAddRows. When the user presses Tab or Enter to enter the data into the 1st cell, this effectively removes the newly added row from the grid leaving the user in the next cell of the newly added (last) row of the grid (without an empty row underneath). Not elegant, but at least mostly functional. Here's the code:
private void uxContactEmailAddressesGrd_CellLeave(object sender, DataGridViewCellEventArgs e)
{
uxContactEmailAddressesGrd.AllowUserToAddRows = false;
}
Maybe one day someone else will come across this problem and find a solution more elegant than mine. That'd be great. Make sure you post your solution to this site for others to use.

How to automatically show beginning of text in TextBox

I have very long text and put it into TextBox. I want to display automatically the beginning of the text not the end. But TextBox automatically show the end of my text.
What can I do to achieve it.
I use SelectionStart method to put cursor at the beginning of text in TextBox in order to implement some simple IntelliSense so preferred solution would not use methods that move cursor.
You could always initially put a smaller value in the textbox then upon your criteria for displaying the full text append the remaining portion of the full text.
Example:
textBox.text = someString.Substring(0, x);
then when needed do
textBox.AppendText(someString.Substring(x+1));
I assume you are using WinForms.
Update: Strange. The struck-out code below workes as described if executed in the form constructor, but not later in the form lifecycle (e.g. a button click handler).
Note that if you have already used SelectionStart to put the cursor at the beginning of the text (e.g., via textBox.SelectionStart = 0;), then all that needs follow is textBox.ScrollToCaret();.
Consider using the textBox.AppendText(someLongString) method when adding text to your text box instead of textBox.Text = someLongString.
If you must wipe out the current text before assigning the new text, use textBox.Text = string.Empty; followed by a call to textBox.AppendText();
You could use owner-draw to override the rendering of the textbox when it doesn't have the input focus. That would trivially give you complete control of what it shows when, without breaking any of the actual editing functionality of the textbox by trying to hack it.
You can send a Win32 scroll message to the underlying textbox handle via P/Invoke:
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
// From User32.dll
private const int WM_VSCROLL = 277;
private const int SB_TOP = 6;
SendMessage(yourTextBox.Handle, WM_VSCROLL, (IntPtr)SB_TOP, IntPtr.Zero);

Show a custom calendar dropdown with a derived DateTimePicker class

My goal is to create a custom DateTimePicker class in .NET 2.0, which shows a custom calendar dropdown instead of the Windows default calendar popup.
By observing Windows messages (see attached code), I am able to find and hide/close the calendar window after creation.
However, a problem remains: After the calendar window is closed, something is still blocking the mouse input. For example, if you try to maximise the owner form of the customised DateTimePicker control after the calendar dropdown has been closed programmatically (attached code), the maximise button does not respond. Only the next click works. Interestingly, the "non-functional click" fires the DTN_CLOSEUP notification, so it appears that the WM_CLOSE did not properly close the calendar.
Any hints on how to accomplish my task are highly appreciated :)
protected override void WndProc(ref System.Windows.Forms.Message m)
{
if (m.Msg == (int)SYSMSG.WM_REFLECT + (int)SYSMSG.WM_NOTIFY)
{
NMHDR nmhdr = (NMHDR)m.GetLParam(typeof(NMHDR));
switch (nmhdr.code)
{
case DTN_DROPDOWN:
// Hide window
IntPtr calHandle = FindWindow("SysMonthCal32", null);
SendMessage(calHandle, (int)SYSMSG.WM_SIZE, 0, SP.Convert.MakeLong(0, 0));
this.BeginInvoke((MethodInvoker)delegate()
{
SendMessage(calHandle, (int)SYSMSG.WM_CLOSE, 0, 0);
});
break;
}
}
base.WndProc(ref m);
}
Instead of sending a WM_CLOSE have you tried sending a DTM_CLOSEMONTHCAL message instead? You would send this to the HWND of the DateTimePicker itself and not the child window. According to the documentation, the DateTime_CloseMonthCal macro sends this message and it seems like what you want to do.
I also don't think you'll need to use BeginInvoke to send it unless there's some problem with closing it in the same dispatch as a drop down notification.
#define DTM_FIRST 0x1000
#define DTM_CLOSEMONTHCAL (DTM_FIRST + 13)
#define DateTime_CloseMonthCal(hdp) SNDMSG(hdp, DTM_CLOSEMONTHCAL, 0, 0)
I finally found this fully customisable datePicker (monthCalendar rendering is override-able) : Culture Aware Month Calendar and Datepicker on CodeProject

Categories

Resources