How do I control two listboxes using a Vertical Scrollbar? - c#

I've got a Windows Form that has 6 listboxes in it.
I'm trying to find / make a code that would make them scroll together. So I dropped a Vertical Scroll Bar onto the form, then put in the following code:
private void vScrollR_Scroll(object sender, ScrollEventArgs e)
{
int i = vScrollR.Value;
lstcr1.SelectedIndex = i;
lstpr1.SelectedIndex = i;
lstsr1.SelectedIndex = i;
lstcr2.SelectedIndex = i;
lstpr2.SelectedIndex = i;
lstsr2.SelectedIndex = i;
}
For some reason though, it won't work (i always returns 0). Am I going about this the wrong way? Is there any other way to achieve what I want? Perhaps, there's a method I need first?
Many thanks to all who will answer.

Change SelectedIndex to TopIndex. I just tried this and it works.
To keep the UI in sync while updating, you can use Control.BeginUpdate and Control.EndUpdate
listBox1.BeginUpdate();
listBox2.BeginUpdate();
listBox1.TopIndex =
listBox2.TopIndex = ++x;
listBox1.EndUpdate();
listBox2.EndUpdate();

Try to Create a Separate Class that Inherits from Listbox.
I hope that this will help you.
using System;
using System.Windows.Forms;
public class myScrollingListBox : ListBox
{
// Event declaration
public delegate void myScrollingListBoxDelegate(object Sender, myScrollingListBoxScrollArgs e);
public event myScrollingListBoxDelegate Scroll;
// WM_VSCROLL message constants
private const int WM_VSCROLL = 0x0115;
private const int SB_THUMBTRACK = 5;
private const int SB_ENDSCROLL = 8;
protected override void WndProc(ref Message m)
{
// Trap the WM_VSCROLL message to generate the Scroll event
base.WndProc(ref m);
if (m.Msg == WM_VSCROLL)
{
int nfy = m.WParam.ToInt32() & 0xFFFF;
if (Scroll != null && (nfy == SB_THUMBTRACK || nfy == SB_ENDSCROLL))
Scroll(this, new myScrollingListBoxScrollArgs(this.TopIndex, nfy == SB_THUMBTRACK));
}
}
public class myScrollingListBoxScrollArgs
{
// Scroll event argument
private int mTop;
private bool mTracking;
public myScrollingListBoxScrollArgs(int top, bool tracking)
{
mTop = top;
mTracking = tracking;
}
public int Top
{
get { return mTop; }
}
public bool Tracking
{
get { return mTracking; }
}
}
}

Related

Windows Forms: ctrl button prevents ListView from scrolling with mouse wheel

I want to scroll ListView with mouse wheel while Ctrl button is pressed. But apparently pressing Ctrl changes scroll behavior: it stops scrolling, possibly tries to apply some zooming logic, I don't know. And I can't find out how to override that.
Please any help or suggestions?
The solution to get mouse wheel scrolling working while Ctrl key is held down is to listen for the WndProc event and specifically detecting MOUSEWHEEL trigger, minimum simple working example:
ListBox with WndProc override
class CtrlListBoxScroll : ListBox
{
private const int WM_HSCROLL = 0x114;
private const int WM_VSCROLL = 0x115;
private const int WM_MOUSEWHEEL = 0x20A;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_MOUSEWHEEL)
{
var scrollDirection = NativeMethods.GET_WHEEL_DELTA_WPARAM(m.WParam);
// scrolling down
if (this.TopIndex < this.Items.Count && scrollDirection < 0)
{
this.TopIndex += 1;
}
// scrolling up
if (this.TopIndex > 0 && scrollDirection > 0)
{
this.TopIndex -= 1;
}
}
}
}
NativeMethods to read the wParam and detect scroll direction
internal static class NativeMethods
{
internal static ushort HIWORD(IntPtr dwValue)
{
return (ushort)((((long)dwValue) >> 0x10) & 0xffff);
}
internal static ushort HIWORD(uint dwValue)
{
return (ushort)(dwValue >> 0x10);
}
internal static int GET_WHEEL_DELTA_WPARAM(IntPtr wParam)
{
return (short)HIWORD(wParam);
}
internal static int GET_WHEEL_DELTA_WPARAM(uint wParam)
{
return (short)HIWORD(wParam);
}
}
Then finally testing it
private void Form1_Load(object sender, EventArgs e)
{
var ctrlListBoxScroll = new CtrlListBoxScroll();
ctrlListBoxScroll.Items.AddRange
(
new object[]
{
"hello", "scroll", "bar", "pressing", "ctrl", "to scroll",
"this", "list", "box", "check", "ctrl", "key", "is", "held"
}
);
this.Controls.Add(ctrlListBoxScroll);
}

Tabpage header stays below arrow buttons after moving

The situation is as follows, I have a custom TabControl that paints the TabHeaders itself so I can choose colors etc.
The problem occurs when I have more TabHeaders then the TabControl can fit and the arrows (spin control) appears. Whenever I then cycle all the way to the right (so there are no more headers to the right) the tabheader that was behind the arrows is still partially there.
Image to demonstrate:
What I know:
If I manually call .Invalidate() so it repaints the problem will go away, however I can't find an event that is triggered by the arrow keys (it does not have a scroll event).
Events that won't work:
Click
Layout
Resize (don't even ask)
I had WndProc in mind (it has cases with get 'spin' which sounds like the spincontrol)
Custom tab control code:
class ColoredTabControl : TabControl
{
public Color ActiveTabColor
{
get
{
return _activeTabColor.Color;
}
set
{
_activeTabColor.Color = value;
this.Invalidate();
}
}
public Color DefaultTabColor
{
get
{
return _defaultTabColor.Color;
}
set
{
_defaultTabColor.Color = value;
this.Invalidate();
}
}
private SolidBrush _activeTabColor = new SolidBrush( Color.LightSteelBlue );
private SolidBrush _defaultTabColor = new SolidBrush( Color.LightGray );
private int _biggestTextHeight = 0;
private int _biggestTextWidth = 0;
public ColoredTabControl()
{
this.SetStyle( ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true );
}
protected override void OnPaint( PaintEventArgs e )
{
base.OnPaint( e );
var graphics = e.Graphics;
TabPage currentTab = this.SelectedTab;
if( TabPages.Count > 0 )
{
var biggestTextHeight = TabPages?.Cast<TabPage>()?.Max( r => TextRenderer.MeasureText( r.Text, r.Font ).Height );
var biggestTextWidth = TabPages?.Cast<TabPage>()?.Max( r => TextRenderer.MeasureText( r.Text, r.Font ).Width );
if( biggestTextHeight > _biggestTextHeight || biggestTextWidth > _biggestTextWidth )
{
_biggestTextWidth = ( int ) biggestTextWidth;
_biggestTextHeight = ( int ) biggestTextHeight;
this.ItemSize = new Size( _biggestTextWidth + 5, _biggestTextHeight + 10 );
}
}
for( int i = 0; i < TabPages.Count; i++ )
{
TabPage tabPage = TabPages[ i ];
Rectangle tabRectangle = GetTabRect( i );
SolidBrush brush = ( tabPage == currentTab ? _activeTabColor : _defaultTabColor );
graphics.FillRectangle( brush, tabRectangle );
TextRenderer.DrawText( graphics, tabPage.Text, tabPage.Font, tabRectangle, tabPage.ForeColor );
}
}
protected override void OnPaintBackground( PaintEventArgs e )
{
base.OnPaintBackground( e );
}
}
A bit of form code to repro the issue:
var tabControl = new ColoredTabControl() { Width = 340, Height = 300, Top = 100 };
tabControl.TabPages.Add( "text" );
tabControl.TabPages.Add( "another tab");
tabControl.TabPages.Add( "yet another" );
tabControl.TabPages.Add( "another one" );
tabControl.TabPages.Add( "another" );
tabControl.DefaultTabColor = Color.Red; //To see the issue clearly
Controls.Add( tabControl );
I ended up fixing it by hooking into WndProc event handler of the msctls_updown32 class. I did this by using a Win32 class I got from a codeproject tabcontrol project by Oscar Londono I had to adjust it a bit for my needs though.
Added code in the tabcontrol:
protected override void OnSelectedIndexChanged( EventArgs e )
{
base.OnSelectedIndexChanged( e );
this.Invalidate();
}
protected override void OnControlAdded( ControlEventArgs e )
{
base.OnControlAdded( e );
FindUpDown();
}
protected override void Dispose( bool disposing )
{
if(scUpDown != null)
{
scUpDown.SubClassedWndProc -= scUpDown_SubClassedWndProc;
}
base.Dispose( disposing );
}
bool bUpDown = false;
SubClass scUpDown = null;
private void FindUpDown()
{
bool bFound = false;
// find the UpDown control
IntPtr pWnd =
Win32.GetWindow( this.Handle, Win32.GW_CHILD );
while( pWnd != IntPtr.Zero )
{
// Get the window class name
char[] fullName = new char[ 33 ];
int length = Win32.GetClassName( pWnd, fullName, 32 );
string className = new string( fullName, 0, length );
if( className == Win32.MSCTLS_UPDOWN32 )
{
bFound = true;
if( !bUpDown )
{
// Subclass it
this.scUpDown = new SubClass( pWnd, true );
this.scUpDown.SubClassedWndProc +=
new SubClass.SubClassWndProcEventHandler(
scUpDown_SubClassedWndProc );
bUpDown = true;
}
break;
}
pWnd = Win32.GetWindow( pWnd, Win32.GW_HWNDNEXT );
}
if( ( !bFound ) && ( bUpDown ) )
bUpDown = false;
}
private int scUpDown_SubClassedWndProc( ref Message m )
{
switch(m.Msg)
{
case Win32.WM_LCLICK:
//Invalidate to repaint
this.Invalidate();
return 0;
}
return 0;
}
The other part of this class is still 100% the same as in the OP
Win32 class:
internal class Win32
{
/*
* GetWindow() Constants
*/
public const int GW_HWNDFIRST = 0;
public const int GW_HWNDLAST = 1;
public const int GW_HWNDNEXT = 2;
public const int GW_HWNDPREV = 3;
public const int GW_OWNER = 4;
public const int GW_CHILD = 5;
public const int WM_NCCALCSIZE = 0x83;
public const int WM_WINDOWPOSCHANGING = 0x46;
public const int WM_PAINT = 0xF;
public const int WM_CREATE = 0x1;
public const int WM_NCCREATE = 0x81;
public const int WM_NCPAINT = 0x85;
public const int WM_PRINT = 0x317;
public const int WM_DESTROY = 0x2;
public const int WM_SHOWWINDOW = 0x18;
public const int WM_SHARED_MENU = 0x1E2;
public const int WM_LCLICK = 0x201;
public const int HC_ACTION = 0;
public const int WH_CALLWNDPROC = 4;
public const int GWL_WNDPROC = -4;
//Class name constant
public const string MSCTLS_UPDOWN32 = "msctls_updown32";
[ DllImport("User32.dll",CharSet = CharSet.Auto)]
public static extern int GetClassName(IntPtr hwnd, char[] className, int maxCount);
[DllImport("User32.dll",CharSet = CharSet.Auto)]
public static extern IntPtr GetWindow(IntPtr hwnd, int uCmd);
}
#region SubClass Classing Handler Class
internal class SubClass : System.Windows.Forms.NativeWindow
{
public delegate int SubClassWndProcEventHandler(ref System.Windows.Forms.Message m);
public event SubClassWndProcEventHandler SubClassedWndProc;
private bool IsSubClassed = false;
public SubClass(IntPtr Handle, bool _SubClass)
{
base.AssignHandle(Handle);
this.IsSubClassed = _SubClass;
}
public bool SubClassed
{
get{ return this.IsSubClassed; }
set{ this.IsSubClassed = value; }
}
protected override void WndProc(ref Message m)
{
if (this.IsSubClassed)
{
if (OnSubClassedWndProc(ref m) != 0)
return;
}
base.WndProc(ref m);
}
private int OnSubClassedWndProc(ref Message m)
{
if (SubClassedWndProc != null)
{
return this.SubClassedWndProc(ref m);
}
return 0;
}
}
#endregion
If anyone has a better answer or a question feel free to answer/comment!
If anyone has a way or even thoughts on how to hook into the actual scroll event of the tabcontrol please answer/comment.
If you are curious to know how to get the scroll event of the tab header when you click on those scroll buttons, you can handle WM_HSCROLL event of the TabControl:
const int WM_HSCROLL = 0x0114;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_HSCROLL)
this.Invalidate();
}
This way you can invalidate the control after scrolling using those buttons.
Also for the cases that user changed selected tab using keyboard, you can override OnSelectedIndexChanged and after calling base method, invalidate the control.

Get MDI client horizontal scroll value

Image:
How to get MDI client horizontal scroll value and other param, when drag MDI child form out of border?
Or how can I get MDI child form absolute coordinates include scrolled part of MDI client?
Found only this solution:
[DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetScrollPos(IntPtr hWnd, int nBar);
public int hpos = 0;
void MdiClient_Scroll(object sender, ScrollEventArgs e)
{
hpos += e.NewValue;
Console.WriteLine(hpos);
}
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 == 0x114)
{ // Trap WM_HSCROLL
var type = (ScrollEventType)(m.WParam.ToInt32() & 0xffff);
var pos = GetScrollPos(this.Handle, SBS_HORZ);//m.WParam.ToInt32() >> 16;
Scroll(this, new ScrollEventArgs(type, oldPos, pos));
oldPos = pos;
}
base.WndProc(ref m);
}
}
now horizontal scroll position always be in hpos variable.

How to restrict form movement to horizontal?

I have a standard form with standard title bar that user may grab and move the form around. In certain situations I want to restrict this movement to horizontal only, so no matter how the mouse actually moves, the form remains on same Y coordinate.
To do this, I catch move event and when I detect deviation from Y, I move the form back to the original Y. Like that:
private void TemplateSlide_Move(object sender, EventArgs e)
{
int y = SlideSettings.LastLocation.Y;
if (y != this.Location.Y)
{
SlideSettings.LastLocation = new Point(this.Location.X, y);
this.Location=Settings.LastLocation;
}
}
But this causes a lot of flicker. Also because form actually moves for a brief moment away from desired Y this causes other issues specific to my program.
Is there a way to prevent form from moving away from desired Y coordinate?
Trap WM_MOVING and modify the RECT structure in LPARAM accordingly.
Something like:
public partial class Form1 : Form
{
public const int WM_MOVING = 0x216;
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
private int OriginalY = 0;
private int OriginalHeight = 0;
private bool HorizontalMovementOnly = true;
public Form1()
{
InitializeComponent();
this.Shown += new EventHandler(Form1_Shown);
this.SizeChanged += new EventHandler(Form1_SizeChanged);
this.Move += new EventHandler(Form1_Move);
}
void Form1_Move(object sender, EventArgs e)
{
this.SaveValues();
}
void Form1_SizeChanged(object sender, EventArgs e)
{
this.SaveValues();
}
void Form1_Shown(object sender, EventArgs e)
{
this.SaveValues();
}
private void SaveValues()
{
this.OriginalY = this.Location.Y;
this.OriginalHeight = this.Size.Height;
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_MOVING:
if (this.HorizontalMovementOnly)
{
RECT rect = (RECT)System.Runtime.InteropServices.Marshal.PtrToStructure(m.LParam, typeof(RECT));
rect.Top = this.OriginalY;
rect.Bottom = rect.Top + this.OriginalHeight;
System.Runtime.InteropServices.Marshal.StructureToPtr(rect, m.LParam, false);
}
break;
}
base.WndProc(ref m);
}
}
When it's appropriate, just use the Mouse's Y coordinate, substituting the X coordinate with your static value. e.g.
... // e.g Mouse Down
originalX = Mouse.X; // Or whatever static X value you have.
... // e.g Mouse Move
// Y is dynamically updated while X remains static
YourObject.Location = new Point(originalX, Mouse.Y);

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