C# - Fix linklabel hand-cursor - c#

I have two link labels in my windows forms program which links to my website.
I got rid of the underlines and the ugly blue colour and tried to fix them up a little bit.
But the biggest problem still remains and It's just so disturbing for me, I don't know why.
The hand cursor when you hover over them is that old Windows 98 hand/link cursor.
Is there any way to change it to the system cursor?
I've checked some other links about this problem, but I couldn't get it to work so I decided to ask here.
Here's my code to get rid of the underline btw:
linkLabel1.LinkBehavior = System.Windows.Forms.LinkBehavior.NeverUnderline;

Unfortunately the LinkLabel class is hard-coded to use Cursors.Hand as the hover cursor.
However, you can work around it by adding a class like this to your project:
public class MyLinkLabel : LinkLabel
{
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
OverrideCursor = Cursors.Cross;
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
OverrideCursor = null;
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
OverrideCursor = Cursors.Cross;
}
}
and using that instead of LinkLabel on your form. (This sets the cursor to a cross for testing purposes, but you can change it to whatever you want.)
I should say that the real LinkLabel code has much more complex logic to do with changing the cursor according to whether or not the link is enabled, but you might not care about that.

Set the Cursor property to Arrow in the properties pane of the LinkLabel in Visual Studio

Update
I prefer Hamido-san's answer here. His solution works properly when the LinkLabel is set to AutoSize = false and works with a LinkArea.
Old solution:
public class LnkLabel : LinkLabel
{
const int WM_SETCURSOR = 32,
IDC_HAND = 32649;
[DllImport("user32.dll")]
public static extern int LoadCursor(int hInstance, int lpCursorName);
[DllImport("user32.dll")]
public static extern int SetCursor(int hCursor);
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_SETCURSOR)
{
int cursor = LoadCursor(0, IDC_HAND);
SetCursor(cursor);
m.Result = IntPtr.Zero; // Handled
return;
}
base.WndProc(ref m);
}
}

Related

Disable image paste in RichTextBox in Winforms

We have an UserControls which herits from the RichTextBox. We would like to forbid the user to enter any image(with copy paste) in this user control.
I found several places where speaking of this:
This doesn't work with Winforms
This will not work when doing a right-click -> Paste
Currently I've this solution:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == (Keys)Shortcut.CtrlV || keyData == (Keys)Shortcut.ShiftIns)
{
if (Clipboard.ContainsImage())
{
return false;
}
}
return base.ProcessCmdKey(ref msg, keyData);
}
Which works for copy paste with CTRL+C-CTRL+V, but not with the contextual menu.
EDIT
I tried the given proposition:
public class CustomRichBox : RichTextBox
{
private const int WM_PASTE = 0x0302;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_PASTE )
{
if (Clipboard.ContainsImage())
{
return;
}
}
base.WndProc(ref m);
}
}
But when I do the CTRL+V, I don't receive this message
Sadly there is no global Paste-Event, on which you can subscribe like in WPF. But maybe this is a solution for you:
hook on default "Paste" event of WinForms TextBox control
This worked for me.
You could try to override the WndProc method to filter the WM_PASTE message:
protected override void WndProc(ref Message m)
{
// Trap WM_PASTE with image:
if (m.Msg == 0x302 && Clipboard.ContainsImage())
{
return;
}
base.WndProc(ref m);
}
EDIT
Unfortunatly, this approach won't work because the RichTextBox control doesn't send the WM_PAINT message to itself. See also: Detecting if paste event occurred inside a rich text box.
As a quick workaround, I tried to copy only the Text (using RichTextBox.Text) in another RichTextBox, then copy the Rtf string in the first RichTextBox, all of that in the "TextChanged" event. However there are a lot of downsides for this workaround. First: is not optimized, second and most important: you lose all text formatting, which might be the reason you chose RichTextBox in the first place, and third: you can still see the image for one or two frames in the RTB until it disappears, and if the user is writing a large text it isn't working very smoothly (but fortunately you can fix this if you copy-paste the code in paste events). However, it turned out to be very useful in my app, which is the reason I posted this answer here.
So here is all the code (assuming you have a RichTextBox named RTB and an auxiliary RichTextBox named auxRTB):
private void RTB_TextChanged(object sender, EventArgs e)
{
int selStart = RTB.SelectionStart;
int selLenght = RTB.SelectionLength;
auxRTB.Text = RTB.Text;
RTB.TextChanged -= RTB_TextChanged;
RTB.Rtf = string.Copy(auxRTB.Rtf);
RTB.TextChanged += RTB_TextChanged;
try
{
RTB.SelectionStart = selStart;
RTB.SelectionLength = selLenght;
}
catch (Exception) { }
}
Now, if you are interested, here I'm going to explain how is that useful in my app. So I built a command system, and the only reason I chose RichTextBox insted of normal TextBox is because I wanted to give different colors to each type of thing from a command. The commands are not meant to be long so I don't have any optimiziation problems, and I don't care about losing formatting, since I always change the colors automatically.
Edit: by the way, here are some links to the same problem on other sites, which might actually help you:
Link 1: https://social.msdn.microsoft.com/Forums/en-US/0f762cb8-7383-4937-8ee8-f8df5d3a9852/disable-image-paste-in-richtextbox?forum=wpf
Link 2: C# / WPF: Richtextbox: Find all Images
Link 3: https://thomaslevesque.com/2015/09/05/wpf-prevent-the-user-from-pasting-an-image-in-a-richtextbox/

How to detect when an MDIClient window has been scrolled

I need to update the position of a child window inside my System.Windows.Forms.MDIClient container when the user scrolls it by dragging the MDIClient's scrollbar thumb.
However I can't find an event that triggers when this happens.
Am I simply missing it, or do I need a workaround, possibly by talking direct to the scrollbar?
I've already tried handling MDIClient.Layout events, but they aren't being triggered by scrolling.
EDIT: I actually only need to know when the scrolling has stopped, in order to change my child window's position.
EDIT2: As a temporary workaround, I'm resetting the child window position on a timer every second, obviously not ideal, but better than nothing. Looks terrible though!
That's possible although a bit awkward. Winforms doesn't make it very easy to find the MdiClient window back and the class itself doesn't expose the Scroll event. That can be worked around, as always in Winforms, you have to sub-class the native MDI client window of your parent window so you can capture the WM_VSCROLL message. This code worked well, paste it into your parent form class:
void MdiClient_Scroll(object sender, ScrollEventArgs e) {
if (e.Type == ScrollEventType.EndScroll) {
// Do your stuff
//...
}
}
private MdiClientWrapper wrapper;
protected override void OnHandleCreated(EventArgs e) {
// Find the MdiClient and sub-class it so we can get the Scroll event
base.OnHandleCreated(e);
if (wrapper != null) wrapper.Scroll -= MdiClient_Scroll;
var client = this.Controls.OfType<MdiClient>().First();
wrapper = new MdiClientWrapper();
wrapper.AssignHandle(client.Handle);
wrapper.Scroll += MdiClient_Scroll;
}
private class MdiClientWrapper : NativeWindow {
public event ScrollEventHandler Scroll;
private int oldPos;
protected override void WndProc(ref Message m) {
if (m.Msg == 0x115) { // Trap WM_VSCROLL
var type = (ScrollEventType)(m.WParam.ToInt32() & 0xffff);
var pos = m.WParam.ToInt32() >> 16;
Scroll(this, new ScrollEventArgs(type, oldPos, pos));
oldPos = pos;
}
base.WndProc(ref m);
}
}

Focus on scroll

I have a user control with a scrollbar (scrollbar appears as a contained user control, which inherits from Panel, is too large). When using the mouse to scroll all is well, but trying to scroll with the mousewheel dont work.
My solution here is to set focus to my child-control in an eventhandler for Scroll. This works. Now the question; Will this result in a lot of unecessary calls to childControl.Focus()? Is there a more neat way of doing this?
Edit: I think I was a bit unclear with my question so Rephrasing the question:
is
private void ChildControl_OnScroll(object sender, ScrollEventArgs scrollEventArgs)
{
this.childControl.Focus();
}
a bad way of setting the focus? I.e. will the focus be set mutliple times each time I scroll? or rather, will this cause (tiny) performance issues.
Here's another approach that gives focus when the scrollbar area of panel1 inside SomeUserControl is clicked. It uses NativeWindow so you don't have to change the panel in your UserControl. This way Focus() will only be called once, when the mouse goes down in the scrollbar area:
public partial class SomeUserControl : UserControl
{
private TrapMouseDownOnScrollArea trapScroll = null;
public SomeUserControl()
{
InitializeComponent();
this.VisibleChanged += new EventHandler(SomeUserControl_VisibleChanged);
}
void SomeUserControl_VisibleChanged(object sender, EventArgs e)
{
if (this.Visible && trapScroll == null)
{
trapScroll = new TrapMouseDownOnScrollArea(this.panel1);
}
}
private class TrapMouseDownOnScrollArea : NativeWindow
{
private Control control = null;
private const int WM_NCLBUTTONDOWN = 0xA1;
public TrapMouseDownOnScrollArea(Control ctl)
{
if (ctl != null && ctl.IsHandleCreated)
{
this.control = ctl;
this.AssignHandle(ctl.Handle);
}
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_NCLBUTTONDOWN:
if (this.control != null)
{
Rectangle screenBounds = control.RectangleToScreen(new Rectangle(0, 0, control.Width, control.Height));
if (screenBounds.Contains(Cursor.Position))
{
control.Focus();
}
}
break;
}
base.WndProc(ref m);
}
}
}
This might be overkill for your scenario, but it demonstrates one way to trap lower level messages. As said before, you could also derive from Panel to achieve the same affect. You could also trap messages at the application level with IMessageFilter.
The MouseWheel event is an event that "bubbles". Windows sends it to the control that has the focus, regardless of where the mouse cursor is located. The most typical problem is that you have a control that cannot receive the focus. A Panel for example.
This changes when you put a control on the panel. Now that control can get the focus and gets the MouseWheel message. It won't have any use for it so the message passes to its parent. Which does have a use for it, the panel scrolls as expected.
You can get a focusable panel control from this answer. A generic "make it work like a browser or Office program" solution from this question
If childControl has a MouseEnter() event then use that instead:
private void childControl_MouseEnter(object sender, EventArgs e)
{
childControl.Focus();
}
Then the mouse wheel events should be direct to childControl.

Disable focus cues on a SplitContainer

How can I disable the focus cues on a SplitContainer?
I ask because I'd rather draw them myself using OnPaint in order to make it look somewhat smoother.
I tried this:
protected override bool ShowFocusCues
{
get
{
return false;
}
}
And this is my control:
public class cSplitContainer : SplitContainer
{
private bool IsDragging;
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (!IsSplitterFixed) IsDragging = true;
Invalidate();
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if (IsDragging)
{
IsDragging = false;
IsSplitterFixed = false;
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (IsDragging)
{
IsSplitterFixed = true;
if (e.Button == MouseButtons.Left)
{
if (Orientation == Orientation.Vertical)
{
if (e.X > 0 && e.X < Width) SplitterDistance = e.X;
}
else
{
if (e.Y > 0 && e.Y < Height) SplitterDistance = e.Y;
}
}
else
{
IsDragging = false;
IsSplitterFixed = false;
}
}
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
base.OnPaint(e);
if (IsDragging)
{
e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(127, 0, 0, 0)), Orientation == Orientation.Horizontal ? new Rectangle(0, SplitterDistance, Width, SplitterWidth) : new Rectangle(SplitterDistance, 0, SplitterWidth, Height));
}
}
}
but it didn't work. I also tried some other methods mentioned before, but I'm still getting focus cues.
I don't think what you are seeing is the FocusCue so much as a floating window that is used to move the slider.
If keyboard access isn't important, you can try making it unselectable:
public class MySplit : SplitContainer {
public MySplit() {
this.SetStyle(ControlStyles.Selectable, false);
}
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.Clear(Color.Red);
}
}
This prevents the SplitContainer from getting focus, but your mouse can still interact with it.
The code of SplitContainer is like:
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
if (Focused) {
DrawFocus(e.Graphics,SplitterRectangle);
}
}
DrawFocus is not virtual. So you can't override it.
Focused is virtual. Maybe you can set it to false while calling base.OnPaint(...) in your OnPaint override.
So you could add following code (I did not tested if it works):
private bool _painting;
public override bool Focused
{
get { return _painting ? false : base.Focused; }
}
protected override void OnPaint(PaintEventArgs e)
{
_painting = true;
try
{
base.OnPaint(e);
}
finally
{
_painting = false;
}
}
That is more a hack than a clean solution.
I was googling for this issue and this question came up on the top.
There is a solution and interesting discussion on a Microsoft forum regarding the splitter stealing focus for no good reason. The following comment is spot on:
The focus issue you mentioned is by design, however to get the performance you want, you can use the following workaround: ....
It may be "by design", but it is not a very good one. What spitters have you ever seen in any Microsoft production application that even temporarily take the focus from the panes they split? I also added the code you suggest, and it does keep me from permanently losing the focus to the splitter, but I still don't like the fact that my panes hide and show their selections during splitter manipulation.
This distracting selection flash just is not present in most professional applications. It is just good enough that it probably won't be worth my time to fix for a while, but not what most people really want. If you respected the TabStop property or even added a AcceptsFocus property, most people would want this off. I think you should add this option to the design in a future version.
--Brendan
Simple solution: give away focus immediately when receiving it!
Three steps:
Create a GotFocus handler for the SplitContainer
Forward the focus to another control with AnotherControl.Focus().
Set TabStop to False
That's all. The ugly focus cue is never shown.
Now, one subtlety: Which other control to give the focus to? It's up to you. Just take the first control by tab order, or a upper-left focusable control in the right pane of the SplitContainer (TextBox in the ASCII diagram below). The perfect solution would be the previous control which had focus, but sadly this is not easy to find out: Find out the control with last focus, but IMHO the upper-left focusable control is a very good response.
left pane right pane
------------------------------------------------
: :: :
: :: [TextBox] [Button] :
: :: :
: :: [Combobox V] :
: :: :
------------------------------------------------
it is a kind a similar question being asked on stackoveflow one solution is sugested as being used by you also along with overriding showfocuscues property you need to override paint method as well.

Undo buffer getting cleared after handling OnKeyDown in TextBox

I am subclassing TextBox:
class Editor : TextBox
I have overridden OnKeyDown, because I want tabs to be replaced by four spaces:
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyCode == Keys.Tab) {
SelectedText = " ";
e.SuppressKeyPress = true;
}
}
This works, but unfortunately it also clears the undo buffer. The end result is that when the user presses tab, Ctrl+Z doesn't work and 'Undo' on the right-click menu becomes disabled. The problem appears to be the "e.SuppressKeyPress = true;" part.
Does anyone have any idea of how to get around this?
For more info, I am creating a fairly simple text editor, and I'm handling not only the Tab key (as above), but also the Enter key. So I have this problem with Tab and Enter. I am aware that this problem doesn't exist with RichTextBox, but for various reasons I want to use TextBox instead.
Any help would be much appreciated, as this is a show-stopping problem in my project.
Thanks,
Tom
This isn't a result of overriding OnKeyDown, it's that you're setting SelectedText (any text modification will have the same effect). You can see this by commenting out your code that sets the SelectedText while leaving everything else. Obviously you won't get a tab or four characters, but the undo buffer will be preserved.
According to this blog post, you should be able to use the Paste(string) function rather than setting the SelectedText property and preserve the undo buffer:
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyCode == Keys.Tab)
{
Paste(" ");
e.SuppressKeyPress = true;
}
}
I've finally found the solution, which is to use the Windows API as follows:
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyCode == Keys.Tab) {
WinApi.SendMessage(Handle, WinApi.WmChar, WinApi.VkSpace, (IntPtr)4);
e.SuppressKeyPress = true;
}
base.OnKeyDown(e);
}
Here is my WinApi class:
using System;
using System.Runtime.InteropServices;
class WinApi
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
public const UInt32 WmChar = 0x102;
public static readonly IntPtr VkSpace = (IntPtr)0x20;
}

Categories