Disable WinForms ListViewItem Tooltip for long texts - c#

I have created ListView and added two item with long text.
when I select first item second item`s text is clipped, for example "MyIte....".
So when move mouse pointer under this item, I see toolTip with all text.
How to disable this tooltip?
Set property ListView.ShowItemToolTips = false doesn't help.

ListView shows item tooltips when receives a WM_Notify message with TTN_NEEDTEXT lparam. So to disable tooltips, you can process ListView messages and if the control received that message, neglect it.
You can inherit your ListView and override WndProc, but as another option, you can register a NativeWindow to receive your ListView messages and this way you can filter messages.
Implementation
public class ListViewToolTipHelper : NativeWindow
{
private ListView parentListView;
private const int WM_NOTIFY = 78;
private const int TTN_FIRST = -520;
private const int TTN_NEEDTEXT = (TTN_FIRST - 10);
public struct NMHDR
{
public IntPtr hwndFrom;
public IntPtr idFrom;
public Int32 code;
}
public bool TooltipEnabled { get; set; }
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_NOTIFY && !TooltipEnabled)
{
var nmHdr = (NMHDR) m.GetLParam(typeof(NMHDR));
if (nmHdr.code == TTN_NEEDTEXT)
return;
}
base.WndProc(ref m);
}
public ListViewToolTipHelper(ListView listView)
{
this.parentListView = listView;
this.AssignHandle(listView.Handle);
}
}
Usage
To disable tooltips for a ListView you can simply create an instance of above class:
ListViewToolTipHelper helper;
helper = new ListViewToolTipHelper(this.listView1);
To enable tooltips again:
helper.TooltipEnabled = true;
Another workaround
You can disable the tooltip for ListView using this workaround, but the side effect is all other tooltips in form also will be disabled this way.
ToolTip toolTip = new ToolTip();
toolTip.SetToolTip(this.listView1, "dummy text");
toolTip.Active = false;

Related

Stop ComboBox Autocomplete feature from happening

I want to stop any trace of autocomplete from happening with my ComboBox.
Is it even possible to stop the Windows Form ComboBox object from auto-completing? Right now when I type the first three characters of something and click another ComboBox to type in the new field, the first ComboBox does not change and keeps those three typed letters. However, if I resize the form then it tries to autocomplete the ComboBox (if there is a matching record that STARTS with the text I typed). Also, the ComboBox's this happens to are Anchored: Left, Right. I have a bad feeling the only way to stop this behavior is to subclass the ComboBox and do something in the resize event or whatever... :/
Here are the settings for what I think are the only properties dealing with autocomplete...
Note: The "Collection" in the AutoCompleteCustomSource has an empty list when I click the three dots to the right.
To Reproduce This Issue:
Create a form and place a standard ComboBox control on it. Then place another one right below it. You will do nothing to the second ComboBox (only used to have a second control to tab to in the example).
In the ComboBox properties find the Items collection and add the following...
testing123
blah321
foobar
In the ComboBox properties find the Anchor property and change to Left, Right.
Run the application.
Test 1: In the ComboBox type bl and press the tab key to get to the next ComboBox. You will see no change.
Test 2: In the ComboBox type bl and resize the form by dragging the right side to make it a little wider. You will see the ComboBox has now been auto filled with blah321.
Found what I was looking for over at https://stackoverflow.com/a/25696213/1039753!
I guess there is no way to stop it without subclassing or making an extension class/method to do it. What I was looking for was "auto complete" when I guess I should have been looking for "auto select". Knowing what to search for is key! This code works great, to use it you would do something like...
ComboBoxAutoSelectEx.AutoSelectOff(ComboBox1);
I could not see any performance hit either. It just looks like magic. I have slightly altered the code from that answer to add the required structure needed.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Opulos.Core.Win32;
namespace MyAppsNamespace {
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public int Left;
public int Top;
public int Right;
public int Bottom;
}
// 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);
}
}

How to draw border for MaskedTextBox using custom color?

I am trying to create a masked textbox to have a border colors.
I tried this below code to achieve it:
public class MaskedTextBoxWithBorder : UserControl
{
MaskedTextBox maskedtextBox;
public MaskedTextBoxWithBorder()
{
maskedtextBox = new MaskedTextBox()
{
BorderStyle = BorderStyle.FixedSingle,
Location = new Point(-1, -1),
Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right
};
Control container = new ContainerControl()
{
Dock = DockStyle.Fill,
Padding = new Padding(-1)
};
container.Controls.Add(maskedtextBox);
this.Controls.Add(container);
DefaultBorderColor = SystemColors.ControlDark;
FocusedBorderColor = Color.Red;
BackColor = DefaultBorderColor;
Padding = new Padding(1);
Size = maskedtextBox.Size;
}
public Color DefaultBorderColor { get; set; }
public Color FocusedBorderColor { get; set; }
public override string Text
{
get
{
return maskedtextBox.Text;
}
set
{
maskedtextBox.Text = value;
}
}
protected override void OnEnter(EventArgs e)
{
BackColor = FocusedBorderColor;
base.OnEnter(e);
}
protected override void OnLeave(EventArgs e)
{
BackColor = DefaultBorderColor;
base.OnLeave(e);
}
protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
{
base.SetBoundsCore(x, y, width, maskedtextBox.PreferredHeight, specified);
}
}
But the problem is it does not have the all features of the masked text box like setting mask type etc.
So I changed my code like this:
public class MaskedTextBoxWithBorder : UserControl
Now I have all features of Masked text box but border colors are not affected.
Is there any way to extend Masked textbox to get border style without losing features something like this which is not possible.
public class MaskedTextBoxWithBorder : UserControl, MaskedTestBox
To draw border of MaskedTextBox you should override WndProc and handle WM_NCPAINT message. Then get the window dc of the control and create a Graphics object from that dc, then draw border for control.This solution has been used also in ToolStripTextBox. The solution can be applied also on a TextBox.
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class MyMaskedTextBox : MaskedTextBox
{
public const int WM_NCPAINT = 0x85;
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_NCPAINT)
{
var hdc = GetWindowDC(this.Handle);
using (var g = Graphics.FromHdcInternal(hdc))
{
g.DrawRectangle(Pens.Blue, new Rectangle(0, 0, Width - 1, Height - 1));
}
ReleaseDC(this.Handle, hdc);
}
}
}
TextBox and MaskedTextBox controls are only wrappers of Win32 TextBox controls so owner drawing them (for custom borders, overlays or anything else) is a bit more complex than normally. Here's what you should do to achieve what you're trying to do.
Derive from MaskedTextBox: public class MaskedTextBoxWithBorder : MaskedTextBox
Get access to the message stream for the Win32 TextBox (it draws itself in response to multiple messages so you need to catch them all, not just the standard WM_PAINT message).
Get the handle to the device context and transform it into a managed Graphics object to draw the border.
Take a look at the following article that explains each step in detail: Adding an Icon or Control to a TextBox or ComboBox
Even though the article discusses the basic TextBox control, it doesn't matter. Both TextBox and MaskedTextBox derive from the TextBoxBase class, which implements all the important parts we're interested in.
Well, usually in a given application, you only change a few property so you might simply add some extra property to your user control for those properties you want to be able to change.
public class MaskedTextBoxWithBorder : UserControl
{
MaskedTextBox maskedtextBox;
// Other existing code...
public string Mask
{
get { return maskedtextBox.Mask; }
set { maskedtextBox.Mask = value; }
}
// Do same thing for other properties you want to change...
}
If you really want to change a lot of properties, then other solutions might be more appropriate...

Winforms ListView TopItem changed event?

Is there any event that fires, when a win form's listview top item property changes?
You would need a Scroll event to detect that the TopItem might have changed. ListView doesn't have one. Which is probably intentional, the class contains some hacks that work around bugs in the native Windows control, hacks that use scrolling.
These hacks should however not matter much in your case since you only look for a change in the TopItem. You'll want to override the WndProc() method so you can get the LVN_ENDSCROLL message. This worked well although I didn't test it thoroughly. Add a new class to your project and paste the code below. Compile. Drop the new control from the top of the toolbox onto your form. Implement the TopItemChanged event.
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
class MyListView : ListView {
public event EventHandler TopItemChanged;
protected virtual void OnTopItemChanged(EventArgs e) {
var handler = TopItemChanged;
if (handler != null) handler(this, e);
}
protected override void WndProc(ref Message m) {
// Trap LVN_ENDSCROLL, delivered with a WM_REFLECT + WM_NOTIFY message
if (m.Msg == 0x204e) {
var notify = (NMHDR)Marshal.PtrToStructure(m.LParam, typeof(NMHDR));
if (notify.code == -181 && !this.TopItem.Equals(lastTopItem)) {
OnTopItemChanged(EventArgs.Empty);
lastTopItem = this.TopItem;
}
}
base.WndProc(ref m);
}
private ListViewItem lastTopItem = null;
private struct NMHDR {
public IntPtr hwndFrom;
public IntPtr idFrom;
public int code;
}
}
There is no event specifically for the TopItem property. However you should be able to get the same effect by caching the previous TopItem result and comparing it on other events which are indicators of item reordering: Paint and DrawItem for example.
private void WatchTopItemChanged(ListView listView, Action callOnChanged) {
var lastTopItem = listView.TopItem;
listView.DrawItem += delegate {
if (lastTopItem != listView.TopItem) {
lastTopItem = listView.TopItem;
callOnChanged();
}
};
}

custom button MouseLeave event

I made a custom button with some panels and pictureboxes. With the MouseEnter and MouseLeave I set the appropriate hover images like normal buttons.
The problem is that if I move the mouse too fast over the control it sometimes doesn't trigger the MouseLeave event. This way the button is "locked" in hover state.
screenshot problem:
http://www.jesconsultancy.nl/images/screens/screen_prblm.png
the button at the right is locked in "hover" state.
How can i solve this?
Thanks.
Holy... That's a mess!
Firstly, UserControls are very buggy. I suggest you make your control inherit from Control instead and draw the image and text yourself.
Secondly, why are you using reflection?
Thirdly, why are there so many controls?
This misses the event because it takes too much to update!
Here is some code for the simples control possible, that will NEVER miss an event:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace lol
{
public class BlackWhiteControl : Control
{
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
this.BackColor = Color.Black;
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
this.BackColor = Color.White;
}
}
}
You should receive the WM_MOUSELEAVE windows message automatically when the mouse leaves the client area and the base class processing of this message will then call the OnMouseLeave method. If this is really not happening you can certainly get around it. Just intercept the WM_MOUSEMOVE directly and then make a Win32 API call that requests you be notified when the mouse leaves your control.
Use the following simple WndProc override...
private bool _mouseOver = false;
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case PI.WM_MOUSEMOVE:
if (!_mouseOver)
{
PI.TRACKMOUSEEVENTS tme = new PI.TRACKMOUSEEVENTS();
tme.cbSize = (uint)Marshal.SizeOf(typeof(PI.TRACKMOUSEEVENTS));
tme.dwHoverTime = 100;
tme.dwFlags = (int)(PI.TME_LEAVE);
tme.hWnd = Handle;
PI.TrackMouseEvent(ref tme);
_mouseOver = true;
}
base.WndProc(ref m);
break;
case PI.WM_MOUSELEAVE:
_mouseOver = false;
base.WndProc(ref m);
break;
}
}
And the platform invoke information you need is...
internal const int WM_MOUSEMOVE = 0x0200;
internal const int WM_MOUSELEAVE = 0x02A3;
internal const int TME_LEAVE = 0x0002;
[StructLayout(LayoutKind.Sequential)]
internal struct TRACKMOUSEEVENTS
{
public uint cbSize;
public uint dwFlags;
public IntPtr hWnd;
public uint dwHoverTime;
}
Hope that helps.

How can I remove the selection border on a ListViewItem

I'm using SetWindowTheme and SendMessage to make a .net listview look like a vista style listview, but the .net control still has a dotted selection border around the selected item:
Selected items in the explorer listview don't have that border around them. How can I remove it?
Windows Explorer:
Edit: Solution:
public static int MAKELONG(int wLow, int wHigh)
{
int low = (int)LOWORD(wLow);
short high = LOWORD(wHigh);
int product = 0x00010000 * (int)high;
int makeLong = (int)(low | product);
return makeLong;
}
SendMessage(olv.Handle, WM_CHANGEUISTATE, Program.MAKELONG(UIS_SET, UISF_HIDEFOCUS), 0);
Telanors solution worked for me. Here's a slightly more self-contained version.
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class MyListView : ListView
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
private const int WM_CHANGEUISTATE = 0x127;
private const int UIS_SET = 1;
private const int UISF_HIDEFOCUS = 0x1;
public MyListView()
{
this.View = View.Details;
this.FullRowSelect = true;
// removes the ugly dotted line around focused item
SendMessage(this.Handle, WM_CHANGEUISTATE, MakeLong(UIS_SET, UISF_HIDEFOCUS), 0);
}
private int MakeLong(int wLow, int wHigh)
{
int low = (int)IntLoWord(wLow);
short high = IntLoWord(wHigh);
int product = 0x10000 * (int)high;
int mkLong = (int)(low | product);
return mkLong;
}
private short IntLoWord(int word)
{
return (short)(word & short.MaxValue);
}
}
Doing this the NON P/Invoke way...
Override your ListView control and add the following:
protected override void OnSelectedIndexChanged(EventArgs e)
{
base.OnSelectedIndexChanged(e);
Message m = Message.Create(this.Handle, 0x127, new IntPtr(0x10001), new IntPtr(0));
this.WndProc(ref m);
}
protected override void OnEnter(EventArgs e)
{
base.OnEnter(e);
Message m = Message.Create(this.Handle, 0x127, new IntPtr(0x10001), new IntPtr(0));
this.WndProc(ref m);
}
Setting the HotTracking property to true hides the focus rectangle. This repro-ed the Explorer style on my Win7 machine:
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
class MyListView : ListView {
public MyListView() {
this.HotTracking = true;
}
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
SetWindowTheme(this.Handle, "explorer", null);
}
[DllImport("uxtheme.dll", CharSet = CharSet.Auto)]
public extern static int SetWindowTheme(IntPtr hWnd, string appname, string subidlist);
}
Beware that getting the items underlined is a side-effect.
Does setting the ListView.ShowFocusCues property to false help?
I know this is rather old, and Windows Forms is antiquated now, but it's still in use and it's still an issue. Worse, none of these solution are elegant, and some don't even work at all.
Here's a very simple solution, when you create your own control that inherits the ListView, then just override the WndProc to never allow focus. It gets rid of all focus-related dotted selection boxes, item selection, subitem selection, etc...
using System.Windows.Forms;
public partial class NoSelectionListView : ListView
{
public NoSelectionListView()
{
InitializeComponent();
}
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x0007) //WM_SETFOCUS
{
return;
}
base.WndProc(ref m);
}
}
It does not seem that there is a particular way to change ListViewItem styles using Windows Forms.
Sometimes there is no way to change some Win32 control behaviors using managed code. The only way is to do some P/Invoke to modify specific behaviors. I find this really tricky but you have no other choice. I often faced this situation when developing Windows Mobile UIs (justly with ListView).
So I have no direct answer to your question but I am pretty sure that if it is not possible using Windows Forms, you can surely do with P/Invoke. The only clues I can give you:
Platform Invoke Tutorial
List View documentation
For me turning focus off didn't work until the control was shown.
I did this:
bool HideFocus { get; set; }
bool _hasEnter;
void OnEnter(object sender, EventArgs e)
{
if (!_hasEnter)
{
_hasEnter = true;
// Selection at startup wont change the actual focus
if (this.SelectedIndices.Count > 0)
this.Items[this.SelectedIndices[0]].Focused = true;
// Hide focus rectangle if requested
if (this.ShowFocusCues && this.HideFocus)
{
var lParam1 = MakeLong(UIS_SET, UISF_HIDEFOCUS);
SendMessage(this.Handle, WM_CHANGEUISTATE, lParam1, 0);
}
}
}
See https://stackoverflow.com/a/15768802/714557 above for the MakeLong call.
Also, any selected items before the control is shown, won't set the selection focus.
I basically used the "Set Focus" event to know that the control was shown and that it was actually getting focus, to make the corretions.

Categories