Synchronized ListViews in .Net - c#

I'm working on a control to tie together the view from one ListView to another so that when the master ListView is scrolled, the child ListView view is updated to match.
So far I've been able to get the child ListViews to update their view when the master scrollbar buttons are clicked. The problem is that when clicking and dragging the ScrollBar itself, the child ListViews are not updated. I've looked at the messages being sent using Spy++ and the correct messages are getting sent.
Here is my current code:
public partial class LinkedListViewControl : ListView
{
[DllImport("User32.dll")]
private static extern bool SendMessage(IntPtr hwnd, UInt32 msg, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll")]
private static extern bool ShowScrollBar(IntPtr hwnd, int wBar, bool bShow);
[DllImport("user32.dll")]
private static extern int SetScrollPos(IntPtr hWnd, int wBar, int nPos, bool bRedraw);
private const int WM_HSCROLL = 0x114;
private const int SB_HORZ = 0;
private const int SB_VERT = 1;
private const int SB_CTL = 2;
private const int SB_BOTH = 3;
private const int SB_THUMBPOSITION = 4;
private const int SB_THUMBTRACK = 5;
private const int SB_ENDSCROLL = 8;
public LinkedListViewControl()
{
InitializeComponent();
}
private readonly List<ListView> _linkedListViews = new List<ListView>();
public void AddLinkedView(ListView listView)
{
if (!_linkedListViews.Contains(listView))
{
_linkedListViews.Add(listView);
HideScrollBar(listView);
}
}
public bool RemoveLinkedView(ListView listView)
{
return _linkedListViews.Remove(listView);
}
private void HideScrollBar(ListView listView)
{
//Make sure the list view is scrollable
listView.Scrollable = true;
//Then hide the scroll bar
ShowScrollBar(listView.Handle, SB_BOTH, false);
}
protected override void WndProc(ref Message msg)
{
if (_linkedListViews.Count > 0)
{
//Look for WM_HSCROLL messages
if (msg.Msg == WM_HSCROLL)
{
foreach (ListView view in _linkedListViews)
{
SendMessage(view.Handle, WM_HSCROLL, msg.WParam, IntPtr.Zero);
}
}
}
}
}
Based on this post on the MS Tech Forums I tried to capture and process the SB_THUMBTRACK event:
protected override void WndProc(ref Message msg)
{
if (_linkedListViews.Count > 0)
{
//Look for WM_HSCROLL messages
if (msg.Msg == WM_HSCROLL)
{
Int16 hi = (Int16)((int)msg.WParam >> 16);
Int16 lo = (Int16)msg.WParam;
foreach (ListView view in _linkedListViews)
{
if (lo == SB_THUMBTRACK)
{
SetScrollPos(view.Handle, SB_HORZ, hi, true);
int wParam = 4 + 0x10000 * hi;
SendMessage(view.Handle, WM_HSCROLL, (IntPtr)(wParam), IntPtr.Zero);
}
else
{
SendMessage(view.Handle, WM_HSCROLL, msg.WParam, IntPtr.Zero);
}
}
}
}
// Pass message to default handler.
base.WndProc(ref msg);
}
This will update the location of the child ListView ScrollBar but does not change the actual view in the child.
So my questions are:
Is it possible to update the child ListViews when the master ListView ScrollBar is dragged?
If so, how?

I wanted to do the same thing, and after searching around I found your code here, which helped, but of course didn't solve the problem. But after playing around with it, I have found a solution.
The key came when I realized that since the scroll buttons work, that you can use that to make the slider work. In other words, when the SB_THUMBTRACK event comes in, I issue repeated SB_LINELEFT and SB_LINERIGHT events until my child ListView gets close to where the master is. Yes, this isn't perfect, but it works close enough.
In my case, my master ListView is called "reportView", while my child ListView is called "summaryView". Here's my pertinent code:
public class MyListView : ListView
{
public event ScrollEventHandler HScrollEvent;
protected override void WndProc(ref System.Windows.Forms.Message msg)
{
if (msg.Msg==WM_HSCROLL && HScrollEvent != null)
HScrollEvent(this,new ScrollEventArgs(ScrollEventType.ThumbTrack, (int)msg.WParam));
base.WndProc(ref msg);
}
}
And then the event handler itself:
reportView.HScrollEvent += new ScrollEventHandler((sender,e) => {
if ((ushort) e.NewValue != SB_THUMBTRACK)
SendMessage(summaryView.Handle, WM_HSCROLL, (IntPtr) e.NewValue, IntPtr.Zero);
else {
int newPos = e.NewValue >> 16;
int oldPos = GetScrollPos(reportView .Handle, SB_HORZ);
int pos = GetScrollPos(summaryView.Handle, SB_HORZ);
int lst;
if (pos != newPos)
if (pos<newPos && oldPos<newPos) do { lst=pos; SendMessage(summaryView.Handle,WM_HSCROLL,(IntPtr)SB_LINERIGHT,IntPtr.Zero); } while ((pos=GetScrollPos(summaryView.Handle,SB_HORZ)) < newPos && pos!=lst);
else if (pos>newPos && oldPos>newPos) do { lst=pos; SendMessage(summaryView.Handle,WM_HSCROLL,(IntPtr)SB_LINELEFT, IntPtr.Zero); } while ((pos=GetScrollPos(summaryView.Handle,SB_HORZ)) > newPos && pos!=lst);
}
});
Sorry about the odd formatting of the while loops there, but that's how I prefer to code things like that.
The next problem was getting rid of the scroll bars in the child ListView. I noticed you had a method called HideScrollBar. This didn't really work for me. I found a better solution in my case was leaving the scroll bar there, but "covering" it up instead. I do this with the column header as well. I just slide my child control up under the master control to cover the column header. And then I stretch the child to fall out of the panel that contains it. And then to provide a bit of a border along the edge of my containing panel, I throw in a control to cover the visible bottom edge of my child ListView. It ends up looking rather nice.
I also added an event handler to sync changing column widths, as in:
reportView.ColumnWidthChanging += new ColumnWidthChangingEventHandler((sender,e) => {
summaryView.Columns[e.ColumnIndex].Width = e.NewWidth;
});
While this all seems a bit of a kludge, it works for me.

This is conjecture just to get the mental juices flowing so take it as you will:
In the scroll handler for the master list, can you call the scroll handler for the child list (passing the sender and eventargs from the master)?
Add this to your Form load:
masterList.Scroll += new ScrollEventHandler(this.masterList_scroll);
Which references this:
private void masterList_scroll(Object sender, System.ScrollEventArgs e)
{
childList_scroll(sender, e);
}
private void childList_scroll(Object sender, System.ScrollEventArgs e)
{
childList.value = e.NewValue
}

I would create my own class, inheriting from ListView to expose the Vertical and Horizontal scroll events.
Then I would do create scroll handlers in my form to synchronize the two controls
This is sample code which should allow a listview to publish scroll events:
public class MyListView : System.Windows.Forms.ListView
{
const int WM_HSCROLL = 0x0114;
const int WM_VSCROLL = 0x0115;
private ScrollEventHandler evtHScroll_m;
private ScrollEventHandler evtVScroll_m;
public event ScrollEventHandler OnHScroll
{
add
{
evtHScroll_m += value;
}
remove
{
evtHScroll_m -= value;
}
}
public event ScrollEventHandler OnHVcroll
{
add
{
evtVScroll_m += value;
}
remove
{
evtVScroll_m -= value;
}
}
protected override void WndProc(ref System.Windows.Forms.Message msg)
{
if (msg.Msg == WM_HSCROLL && evtHScroll_m != null)
{
evtHScroll_m(this,new ScrollEventArgs(ScrollEventType.ThumbTrack, msg.WParam.ToInt32()));
}
if (msg.Msg == WM_VSCROLL && evtVScroll_m != null)
{
evtVScroll_m(this, new ScrollEventArgs(ScrollEventType.ThumbTrack, msg.WParam.ToInt32()));
}
base.WndProc(ref msg);
}
Now handle the scroll events in your form:
Set up a PInvoke method to be able to send a windows message to a control:
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int SendMessage(IntPtr hWnd, [MarshalAs(UnmanagedType.U4)] int iMsg, int iWParam, int iLParam);
Set up your event handlers (lstMaster and lstChild are two listboxes):
lstMaster.OnVScroll += new ScrollEventHandler(this.lstMaster_OnVScroll);
lstMaster.OnHScroll += new ScrollEventHandler(this.lstMaster_OnHScroll);
const int WM_HSCROLL = 0x0114;
const int WM_VSCROLL = 0x0115;
private void lstMaster_OnVScroll(Object sender, System.ScrollEventArgs e)
{
SendMessage(lstChild.Handle,WM_VSCROLL,(IntPtr)e.NewValue, IntPtr.Zero);
}
private void lstMaster_OnHScroll(Object sender, System.ScrollEventArgs e)
{
SendMessage(lstChild.Handle,WM_HSCROLL,(IntPtr)e.NewValue, IntPtr.Zero);
}

A naive solution to your problem can be handling the paint message in the parent list view and checking if the linked list views are displaying the correct data. If they don't, then update them to display the correct data by calling the EnsureVisible method.

Related

To prevent entering special characters on textbox ( Already have thousand of textboxes in solution )

We have a advanced software written by using c# ( windows forms ). In their we have 1000 or more textboxes. I need to validate user input on all these textboxes to stop entering special characters and any scripts. Textboxes are hard coded.
for eg :
I can use following piece of code on every keypress to check whether user has entered the allowed characters or not.
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
var regex = new Regex(#"[^a-zA-Z0-9\s]");
if (regex.IsMatch(e.KeyChar.ToString()))
{
e.Handled = true;
}
}
but then we have to implement this on every textboxes key press events ( if there is no other solution this is the last thing to do). Is there any way to handle this from a single place and affect for every textboxes (on some places textboxes have their own key press events as well). What I need is a common method which will be fire on every key press events of any textbox.
Solution : Create a Custom Control derived from TextBox (or TextBoxBase) that contains all the logic required for my validation, so it's all done in one place.
But still I have to again change all the existing textboxes my new textbox.
Is there any way to change behavior of current event handler?
Note: What I need is to override current keypress event of the textbox and run my validation code plus need to run if there is any explicitly mentioned code inside key press events.
If you want to add the KeyDown Event to all TextBoxes you can loop through them and add the same EventHandler for all of them.
To do that first of all we need to create a function that loop through all our TextBoxes.
GetChildControls Function:
public static IEnumerable<TControl> GetChildControls<TControl>(this Control control)
where TControl : Control
{
var children = (control.Controls != null) ?
control.Controls.OfType<TControl>() : Enumerable.Empty<TControl>();
return children.SelectMany(c => GetChildControls<TControl>(c)).Concat(children);
}
We can now use that function after the InitializeComponent(); to assign the Txt_KeyDown() EventHandler to all TextBoxes.
Calling the Function:
public Example() {
InitializeComponent();
var allTextBoxes = this.GetChildControls<TextBox>();
foreach (TextBox tb in allTextBoxes)
{
tb.KeyDown += Txt_KeyDown;
}
}
private void Txt_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) {
// Your code here
}
"Is there any way to handle this from a single place and affect for
every textboxes"
There's a few ways. But it seems you don't want to edit the textbox itself, so there's only one reliable way I'm aware of; attaching a global keyboard hook. Code follows:
class GlobalKeyboardHook
{
#region DLL Imports
[DllImport("user32.dll")]
static extern IntPtr SetWindowsHookEx(int hookEventId, keyboardProc callback, IntPtr handleInstance, uint threadId);
[DllImport("user32.dll")]
static extern bool UnhookWindowsHookEx(IntPtr handleInstance);
[DllImport("user32.dll")]
static extern int CallNextHookEx(IntPtr ignoredParameter, int hookCode, int wParam, ref KeyboardHookStruct lParam);
[DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string libFileName);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
#endregion DLL Imports
#region Class Declarations
private delegate int keyboardProc(int code, int wParam, ref KeyboardHookStruct lParam);
private keyboardProc kbdProc;
public struct KeyboardHookStruct
{
public int vkCode;
public int scanCode;
public int flags;
public int time;
public int extraInfo;
}
private static class KeyboardMessages
{
public const int WH_KEYBOARD_LL = 13;
public const int WM_KEYDOWN = 0x100;
public const int WM_KEYUP = 0x101;
public const int WM_SYSKEYDOWN = 0x104;
public const int WM_SYSKEYUP = 0x105;
}
IntPtr HookPointer = IntPtr.Zero;
IntPtr ModuleInstance = IntPtr.Zero;
public event KeyEventHandler KeyDown;
public event KeyEventHandler KeyUp;
#endregion Class Declarations
#region Class Functions
public GlobalKeyboardHook() {
EnableHook(true, null);
}
public GlobalKeyboardHook(Process P) {
EnableHook(true, P);
}
~GlobalKeyboardHook() {
EnableHook(false, null);
}
public void EnableHook(bool Enabled)
{
EnableHook(Enabled, null);
}
public void EnableHook(bool Enabled, Process P) {
if (Enabled)
{
HookPointer = SetWindowsHookEx(KeyboardMessages.WH_KEYBOARD_LL, kbdProc = HookCallback, ModuleInstance = P == null ? LoadLibrary("User32") : GetModuleHandle(P.MainModule.ModuleName), 0);
}
else
{
UnhookWindowsHookEx(HookPointer);
HookPointer = IntPtr.Zero;
ModuleInstance = IntPtr.Zero;
kbdProc = null;
}
}
public int HookCallback(int code, int wParam, ref KeyboardHookStruct lParam) {
if (code >= 0) {
KeyEventArgs key = new KeyEventArgs((Keys)lParam.vkCode);
if ((wParam == KeyboardMessages.WM_KEYDOWN || wParam == KeyboardMessages.WM_SYSKEYDOWN) && (KeyDown != null)) {
KeyDown(this, key) ;
} else if ((wParam == KeyboardMessages.WM_KEYUP || wParam == KeyboardMessages.WM_SYSKEYUP) && (KeyUp != null)) {
KeyUp(this, key);
}
if (key.Handled)
return 1;
}
return CallNextHookEx(HookPointer, code, wParam, ref lParam);
}
#endregion Class Functions
}
To activate we add the following:
GlobalKeyboardHook ghk = new GlobalKeyboardHook(Process.GetCurrentProcess());
Type tbType = typeof(TextBox);
ghk.KeyDown += new KeyEventHandler(() => {
if (typeof(this.ActiveControl) == tbType)
RunValidation(this.ActiveControl.Text);
});
Once you have the boilerplate hook, adding validation becomes pretty simple. No loops means you're not wasting processor time iterating over a thousand text boxes.
Just remember this will apply to ALL controls of type TextBox within the current process. If you add a custom TextBox control or don't want to check all of them - that should be accounted for prior to calling RunValidation().

Raise an event when I hover the mouse over a ComboBox item

I'm not able to find an event to fire when I hover my ComboBox Items.
I'm using windows form to build an application.
I found a something similar for WPF:
how to change label text when I hover mouse over a combobox item?.
How can I do it the similar way in Windows Forms, or is there an alternate way?
Class ComboBoxListEx:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
[DesignerCategory("Code")]
public class ComboBoxListEx : ComboBox
{
private int listItem = -1;
private const int CB_GETCURSEL = 0x0147;
public event EventHandler<ListItemSelectionChangedEventArgs> ListItemSelectionChanged;
protected virtual void OnListItemSelectionChanged(ListItemSelectionChangedEventArgs e)
=> this.ListItemSelectionChanged?.Invoke(this, e);
public ComboBoxListEx() { }
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg)
{
case CB_GETCURSEL:
int selItem = m.Result.ToInt32();
if (listItem != selItem)
{
listItem = selItem;
OnListItemSelectionChanged(new ListItemSelectionChangedEventArgs(
listItem, listItem < 0 ? string.Empty : this.GetItemText(this.Items[listItem]))
);
}
break;
default:
// Add Case switches to handle other events
break;
}
}
public class ListItemSelectionChangedEventArgs : EventArgs
{
public ListItemSelectionChangedEventArgs(int idx, string text)
{
this.ItemIndex = idx;
this.ItemText = text;
}
public int ItemIndex { get; private set; }
public string ItemText { get; private set; }
}
}
private void comboBoxListEx1_ListItemSelectionChanged(object sender, ComboBoxListEx.ListItemSelectionChangedEventArgs e)
{
label15.Text = e.ItemText;
}
You can create a Custom Control, derived from ComboBox, override its WndProc method to intercept the CB_GETCURSEL message.
Call base.WndProc(ref m) first. When the message is processed, the Message object's m.Result property is set to a value (as IntPtr) that represents the Item currently tracked in the ListBox (the Item highlighted when the Mouse Pointer hovers it).
► Note: prior to .Net Framework 4.8, the CB_GETCURSEL message result is not bubbled up automatically: we must send LB_GETCURSEL to the child ListBox to get the index of the Item currently highlighted.
The ListBox handle is retrieved using GetComboBoxInfo: it could be also accessed using reflection (the private ChildListAutomationObject property returns the ListBox AutomationElement, which provides the handle), or sending a CB_GETCOMBOBOXINFO message (but it's the same as calling GetComboBoxInfo()).
This custom ComboBox raises an Event, ListItemSelectionChanged, with a custom EventArgs object, ListItemSelectionChangedEventArgs, which exposes two public properties: ItemIndex and ItemText, set to the Index and Text of the hovered item.
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
[DesignerCategory("Code")]
public class ComboBoxListEx : ComboBox
{
private const int CB_GETCURSEL = 0x0147;
private int listItem = -1;
IntPtr listBoxHandle = IntPtr.Zero;
public event EventHandler<ListItemSelectionChangedEventArgs> ListItemSelectionChanged;
protected virtual void OnListItemSelectionChanged(ListItemSelectionChangedEventArgs e)
=> this.ListItemSelectionChanged?.Invoke(this, e);
public ComboBoxListEx() { }
// .Net Framework prior to 4.8 - get the handle of the ListBox
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
listBoxHandle = GetComboBoxListInternal(this.Handle);
}
protected override void WndProc(ref Message m)
{
int selItem = -1;
base.WndProc(ref m);
switch (m.Msg) {
case CB_GETCURSEL:
selItem = m.Result.ToInt32();
break;
// .Net Framework prior to 4.8
// case CB_GETCURSEL can be left there or removed: it's always -1
case 0x0134:
selItem = SendMessage(listBoxHandle, LB_GETCURSEL, 0, 0);
break;
default:
// Add Case switches to handle other events
break;
}
if (listItem != selItem) {
listItem = selItem;
OnListItemSelectionChanged(new ListItemSelectionChangedEventArgs(
listItem, listItem < 0 ? string.Empty : GetItemText(Items[listItem]))
);
}
}
public class ListItemSelectionChangedEventArgs : EventArgs
{
public ListItemSelectionChangedEventArgs(int idx, string text) {
ItemIndex = idx;
ItemText = text;
}
public int ItemIndex { get; private set; }
public string ItemText { get; private set; }
}
// -------------------------------------------------------------
// .Net Framework prior to 4.8
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern bool GetComboBoxInfo(IntPtr hWnd, ref COMBOBOXINFO pcbi);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int SendMessage(IntPtr hWnd, uint uMsg, int wParam, int lParam);
private const int LB_GETCURSEL = 0x0188;
[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;
public void Init() => this.cbSize = Marshal.SizeOf<COMBOBOXINFO>();
}
internal static IntPtr GetComboBoxListInternal(IntPtr cboHandle)
{
var cbInfo = new COMBOBOXINFO();
cbInfo.Init();
GetComboBoxInfo(cboHandle, ref cbInfo);
return cbInfo.hwndList;
}
}
Works like this:

How to force ComboBox to scroll to top?

I have a winforms ToolStripComboBox with a ComboBox property. By default, it seems to auto-scroll to the selected index. See screenshot below:
On form load, I'm setting SelectedIndex to 1, which is what needs to happen. But I want the first item in the list (SelectedIndex 0) to be visible, or in other words auto-scroll to the very top. I can't find any way to force the combobox to scroll to the top by default, or to do so programmatically. There is an AutoScrollOffset property on ComboBox which I have experimented with, but it seems to do nothing, no matter what I set it to.
As seen in my screenshot above, I want to force the combobox (either via property or method call) to appear like the 2nd pic in which the top item (All - All Categories) is visible, while still leaving index 1 selected.
How can this be done?
When you open the dropdown, a LB_SETTOPINDEX message will be sent to the list which is in the dropdown menu. This message is responsible to setting the top index in the list.
You can handle this message and change its WParam to Intptr.Zero to always use 0 as top index.
Native Methods
Here is a class which contains native methods, structures and constants to manipulate the combo box for this purpose:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class NativeMethods
{
[StructLayout(LayoutKind.Sequential)]
public struct COMBOBOXINFO
{
public int cbSize;
public RECT rcItem;
public RECT rcButton;
public int stateButton;
public IntPtr hwndCombo;
public IntPtr hwndEdit;
public IntPtr hwndList;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left; public int Top; public int Right; public int Bottom;
}
[DllImport("user32.dll")]
public static extern bool GetComboBoxInfo(IntPtr hWnd, ref COMBOBOXINFO pcbi);
public class ListBoxHelper : NativeWindow
{
private const int LB_SETTOPINDEX = 0x0197;
public ListBoxHelper(IntPtr hwnd) { this.AssignHandle(hwnd); }
protected override void WndProc(ref Message m)
{
if (m.Msg == LB_SETTOPINDEX)
m.WParam = IntPtr.Zero;
base.WndProc(ref m);
}
}
}
ComboBox
Here is a ComboBox which its dropdown always opens showing item 0 as top item:
public class MyComboBox : ComboBox
{
NativeMethods.ListBoxHelper listBoxHelper;
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
var info = new NativeMethods.COMBOBOXINFO();
info.cbSize = Marshal.SizeOf(info);
NativeMethods.GetComboBoxInfo(this.Handle, ref info);
listBoxHelper = new NativeMethods.ListBoxHelper(info.hwndList);
}
}
ToolStripComboBox
ToolStripComboBox hosts a ComboBox inside. So the solution is similar:
public class MyToolStripComboBox : ToolStripComboBox
{
public MyToolStripComboBox()
{
this.Control.HandleCreated += Control_HandleCreated;
}
NativeMethods.ListBoxHelper listBoxHelper;
private void Control_HandleCreated(object sender, EventArgs e)
{
base.OnVisibleChanged(e);
var info = new NativeMethods.COMBOBOXINFO();
info.cbSize = Marshal.SizeOf(info);
NativeMethods.GetComboBoxInfo(this.Control.Handle, ref info);
listBoxHelper = new NativeMethods.ListBoxHelper(info.hwndList);
}
}

Check Scrollbar Visibility

Is there a way to check if the Vertical Scroll Bar is visible on a certain ListView object?
I got a Windows Forms with a listView on it, on the resize event i would like to catch if the listview has its Vertical Scroll Bar visible!
If this is WPF a sample exist here which at the base of the solution hooks on to ListView.LayoutUpdated.
If this is WinForms you could make use of pinvoke and GetWindowLong...
static public class WndInfo
{
[DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
...
public static bool IsWindowTopMost(IntPtr Handle)
{
return (GetWindowLong(Handle, GWL_EXSTYLE) & WS_EX_TOPMOST) != 0;
}
...
}
VB code exists using GetWindowLong to check if a ScrollBar exist which you could port to C#.
I have several methods that I use when using Winforms that are based on what type of control I would like to get that information from. Here is my class.
public static class NativeMethods
{
const Int32 LVM_FIRST = 0x1000;
const Int32 LVM_SCROLL = LVM_FIRST + 20;
[DllImport("user32")]
static extern IntPtr SendMessage(IntPtr Handle, Int32 msg, IntPtr wParam, IntPtr lParam);
// offset of window style value
const int GWL_STYLE = -16;
// window style constants for scrollbars
const int WS_VSCROLL = 0x00200000;
const int WS_HSCROLL = 0x00100000;
[DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
static ScrollBars GetControlVisibleScrollbars(Control ctl)
{
int wndStyle = GetWindowLong(ctl.Handle, GWL_STYLE);
bool hsVisible = (wndStyle & WS_HSCROLL) != 0;
bool vsVisible = (wndStyle & WS_VSCROLL) != 0;
if(hsVisible)
return vsVisible ? ScrollBars.Both : ScrollBars.Horizontal;
else
return vsVisible ? ScrollBars.Vertical : ScrollBars.None;
}
public static ScrollBars GetVisibleScrollbars(this ListView lv)
{
if(lv is null)
{
throw new ArgumentNullException(nameof(lv));
}
return GetControlVisibleScrollbars(lv);
}
public static ScrollBars GetVisibleScrollbars(this ScrollableControl ctl)
{
if(ctl is null)
{
throw new ArgumentNullException(nameof(ctl));
}
if(ctl.HorizontalScroll.Visible)
return ctl.VerticalScroll.Visible ? ScrollBars.Both : ScrollBars.Horizontal;
else
return ctl.VerticalScroll.Visible ? ScrollBars.Vertical : ScrollBars.None;
}
private static void ScrollHorizontal(Form form, int pixelsToScroll)
{
SendMessage(form.Handle, LVM_SCROLL, (IntPtr)pixelsToScroll, IntPtr.Zero);
}
public static void EnsureVisible(this ListViewItem item, int subItemIndex, int margin=10)
{
if(item is null)
{
throw new ArgumentNullException(nameof(item));
}
if( subItemIndex > item.SubItems.Count - 1)
{
throw new IndexOutOfRangeException($"ListView {item.ListView.Name} does not have a SubItem on index {subItemIndex}");
}
// scroll to the item row.
item.EnsureVisible();
Rectangle bounds = item.SubItems[subItemIndex].Bounds;
bounds.Width = item.ListView.Columns[subItemIndex].Width;
ScrollToRectangle(item.ListView,bounds,margin);
}
private static void ScrollToRectangle(ListView listView, Rectangle bounds, int margin)
{
int scrollToLeft = bounds.X + bounds.Width + margin;
if(scrollToLeft > listView.Bounds.Width)
{
ScrollHorizontal(listView.FindForm(),scrollToLeft - listView.Bounds.Width);
}
else
{
int scrollToRight = bounds.X - margin;
if(scrollToRight < 0)
{
ScrollHorizontal(listView.FindForm(),scrollToRight);
}
}
}
Say in your use case you have a splitter panel and you would like to have the left panel wide enough not to have a horizontal scrollbar then you could do this:
var bar= MyListView.GetVisibleScrollbars();
while(bar== ScrollBars.Horizontal || bar== ScrollBars.Both)
{
progressPanels.SplitterDistance += 5;
bar = MyListView.GetVisibleScrollbars();
}
I'm not saying it's the best way, I just say it's an option, especially if dealing with several monitors in different DPI

Hiding dashed outline around trackbar control when selected

In C# winforms, is there a way to not show the dashed focus outline border that shows around a trackbar control when it is being used?
Details: This outline looks kinda tacky to me, so I'm just shooting for aesthetics to not show it.
Thanks,
Adam
ShowFocusCues didn't work for me, but this did:
internal class NoFocusTrackBar : System.Windows.Forms.TrackBar
{
[System.Runtime.InteropServices.DllImport("user32.dll")]
public extern static int SendMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
private static int MakeParam(int loWord, int hiWord)
{
return (hiWord << 16) | (loWord & 0xffff);
}
protected override void OnGotFocus(EventArgs e)
{
base.OnGotFocus(e);
SendMessage(this.Handle, 0x0128, MakeParam(1, 0x1), 0);
}
}
See documentation on WM_UPDATEUISTATE for how this works (basically sending a message to turn the dumb thing off the trackbar gets the focus).
I know it's an old question but this is simpler if anyone interested:
public class TrackBarWithoutFocus : TrackBar
{
private const int WM_SETFOCUS = 0x0007;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_SETFOCUS)
{
return;
}
base.WndProc(ref m);
}
}
private void trackBar1_MouseLeave(object sender, EventArgs e)
{
button2.Select();
}
really old but i made a button with visible set to false and selected that one
when leaving trackbar area

Categories

Resources