I have a listview that generates thumbnail using a backgroundworker. When the listview is being scrolled i want to pause the backgroundworker and get the current value of the scrolled area, when the user stopped scrolling the listview, resume the backgroundworker starting from the item according to the value of the scrolled area.
Is it possible to handle scroll event of a listview? if yes how? if not then what is a good alternative according to what i described above?
You'll have to add support to the ListView class so you can be notified about scroll events. Add a new class to your project and paste the code below. Compile. Drop the new listview control from the top of the toolbox onto your form. Implement a handler for the new Scroll event.
using System;
using System.Windows.Forms;
class MyListView : ListView {
public event ScrollEventHandler Scroll;
protected virtual void OnScroll(ScrollEventArgs e) {
ScrollEventHandler handler = this.Scroll;
if (handler != null) handler(this, e);
}
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
if (m.Msg == 0x115) { // Trap WM_VSCROLL
OnScroll(new ScrollEventArgs((ScrollEventType)(m.WParam.ToInt32() & 0xffff), 0));
}
}
}
Beware that the scroll position (ScrollEventArgs.NewValue) isn't meaningful, it depends on the number of items in the ListView. I forced it to 0. Following your requirements, you want to watch for the ScrollEventType.EndScroll notification to know when the user stopped scrolling. Anything else helps you detect that the user started scrolling. For example:
ScrollEventType mLastScroll = ScrollEventType.EndScroll;
private void myListView1_Scroll(object sender, ScrollEventArgs e) {
if (e.Type == ScrollEventType.EndScroll) scrollEnded();
else if (mLastScroll == ScrollEventType.EndScroll) scrollStarted();
mLastScroll = e.Type;
}
Based upon the post that #Adriaan Stander posted my class for raising scroll events is below.
internal class ControlScrollListener : NativeWindow, IDisposable
{
public event ControlScrolledEventHandler ControlScrolled;
public delegate void ControlScrolledEventHandler(object sender, EventArgs e);
private const uint WM_HSCROLL = 0x114;
private const uint WM_VSCROLL = 0x115;
private readonly Control _control;
public ControlScrollListener(Control control)
{
_control = control;
AssignHandle(control.Handle);
}
protected bool Disposed { get; set; }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (Disposed) return;
if (disposing)
{
// Free other managed objects that implement IDisposable only
}
// release any unmanaged objects
// set the object references to null
ReleaseHandle();
Disposed = true;
}
protected override void WndProc(ref Message m)
{
HandleControlScrollMessages(m);
base.WndProc(ref m);
}
private void HandleControlScrollMessages(Message m)
{
if (m.Msg == WM_HSCROLL | m.Msg == WM_VSCROLL)
{
if (ControlScrolled != null)
{
ControlScrolled(_control, new EventArgs());
}
}
}
}
Use it like so...
Declare a field:
private ControlScrollListener _processListViewScrollListener;
Instantiate it with the controls which you need to know issrolling:
_processListViewScrollListener = new ControlScrollListener(ProcessesListView);
Wire in a handler:
_processListViewScrollListener.ControlScrolled += ProcessListViewScrollListener_ControlScrolled;
Handler the event:
void ProcessListViewScrollListener_ControlScrolled(object sender, EventArgs e)
{
// do what you need to do
}
The event args in the event raised could be tweaked to contain more useful information. I just needed to know my control had been scrolled!
See this post ListView Scroll Event
Use the native window class to listen
for the scroll messages on the
listbox. Will work with any control.
Catching the scroll event now is easily done in .net 4.
Catch the Loaded event from your ListView (m_ListView) and do this:
if (VisualTreeHelper.GetChildrenCount(m_ListView) != 0)
{
Decorator border = VisualTreeHelper.GetChild(m_ListView, 0) as Decorator;
ScrollViewer sv = border.Child as ScrollViewer;
sv.ScrollChanged += ScrollViewer_ScrollChanged;
}
then, implement your ScrollViewer_ScrollChanged function:
private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
...
}
Related
I've a user control, pretty simple just a label and a picturebox, they are docked that you can't even see the underlying
most of the events never occurs (click, hover and others)
i think (i could be so wrong), it's because the events won't be triggered as they are being clicked either on the label or the picturebox and not on the background
i need somehow to redirect these events (i.e create that events in the usercontrol and somehow redirect them to my code and with my code i mean the form that uses them) or any other way to capture those events
i've seen like 5 topics about the same problem and none were solved, any workaround ?
You need to redirect your Click event of your label and pictureBox to the OnClick event of your user control using this code:
public UserControl1()
{
InitializeComponent();
this.label1.Click += myClickEvent;
this.pictureBox1.Click += myClickEvent;
}
private void myClickEvent(object sender, EventArgs e)
{
this.OnClick(e);
}
If you can Inherit the Label and PictureBox, Inherit them and override WndProc and do the following.
protected override void WndProc(ref Message m)
{
if (m.Msg == (int)WindowsMessage.WM_NCHITTEST)
{
m.Result = (IntPtr)(-1);//Transparent
return;
}
base.WndProc(ref m);
}
If not, Use this
public partial class MyUserControl : UserControl
{
private TransparentWindow label;
private TransparentWindow pic;
public MyUserControl()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
label = new TransparentWindow(label1);
pic = new TransparentWindow(pictureBox1);
}
}
class TransparentWindow : NativeWindow
{
public TransparentWindow(Control control)
{
this.AssignHandle(control.Handle);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == (int)WindowsMessage.WM_NCHITTEST)
{
m.Result = (IntPtr)(-1);//Transparent
return;
}
base.WndProc(ref m);
}
}
WindowMessage enumeration can be found here
Related question
The answer from Matin Lotfaliee is definetly correct, but in my case, where i want to forward an event from one control to 'OtherControl', you may prefer one-liners:
this.OtherControl.Click += (s, e) => { this.OnClick(e); };
I have added this
public class MyListView : ListView
{
public event EventHandler<EventArgs> Scrolled;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
const int wm_vscroll = 0x115;
if (m.Msg == wm_vscroll && Scrolled != null)
{
Scrolled(this, new EventArgs());
}
}
}
and when i scroll the mouse wheel it scroll the list perfectly, my scroll event doesn't fire.
I have tried hooking MouseWheel, but the scroll happens after the mousewheel event returns to windows, but still doesn't call Scroll event.
EDIT:-
I have added an OnMouseWheel event that calls my update code, but this is called before the visible area is scrolled, so my update code misses some parts.
I want the mousewheel event to scroll the visible area then call the onScroll event,
OR
for the onscroll event to get called as a by product of the mousewheel scrolling the visible area
Trapping WM_MOUSEWHEEL worked for me:
public class MyListView : ListView
{
private const int WM_MOUSEWHEEL = 0x20a;
public event EventHandler<EventArgs> Scrolled;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_MOUSEWHEEL && Scrolled != null)
{
Scrolled(this, new EventArgs());
}
}
}
default listview's visual tree have ScrollViewer control.
ScrollViewer override OnMouseWheel and set e.handled = true,
if you want to handle this event, you must use EventManager.RegisterClassHandler(Type classType, RoutedEvent routedEvent,Delegate handler,true);
true to invoke this class handler even if arguments of the routed event have been marked as handled;
try it:
class CustomListView: ListView
{
public CustomListView()
{
EventManager.RegisterClassHandler(typeof(CustomListView), MouseWheelEvent, new RoutedEventHandler(OnMouseWheel), true);
}
internal static void OnMouseWheel(object sender, RoutedEventArgs e)
{
//Do something you want
}
}
Say in winforms I have a listbox.
and I also have a thread which waits till there is some item in listbox.
Say currently the list box is empty, so the thread have to wait.
Say now there is some item in listbox the thread has to begin execution.
Did you think of Timers which can periodic checks the listbox items if there is some item than
start your thread and stop checking .
You could use threads, but I think you will be better off with an event-based method for processing. The ListBox class doesn't have an event for adding items by default, but you can extend the class to make your own. Here's an example of how you would do that:
public class MyListBox : ListBox
{
private const int LB_ADDSTRING = 0x180;
private const int LB_INSERTSTRING = 0x181;
protected override void WndProc(ref Message m)
{
if (m.Msg == LB_ADDSTRING || m.Msg == LB_INSERTSTRING)
{
OnItemAdded(this, new EventArgs());
}
base.WndProc(ref m);
}
public event EventHandler ItemAdded;
protected void OnItemAdded(object sender, EventArgs e)
{
if (ItemAdded != null)
ItemAdded(sender, e);
}
}
Once you have made this class, just use it on your form.
public partial class Form1 : Form
{
MyListBox lb = new MyListBox();
public Form1()
{
InitializeComponent();
this.Controls.Add(lb);
lb.ItemAdded += lb_ItemAdded;
}
void lb_ItemAdded(object sender, EventArgs e)
{
// process item here...
}
}
I have a panel in my Form with a click event handler. I also have some other controls inside the panel (label, other panels, etc.). I want the click event to register if you click anywhere inside the panel. The click event works as long as I don't click on any of the controls inside the panel but I want to fire the event no matter where you click inside the panel. Is this possible without adding the same click event to all of the controls inside the panel?
Technically it is possible, although it is very ugly. You need to catch the message before it is sent to the control that was clicked. Which you can do with IMessageFilter, you can sniff an input message that was removed from the message queue before it is dispatched. Like this:
using System;
using System.Drawing;
using System.Windows.Forms;
class MyPanel : Panel, IMessageFilter {
public MyPanel() {
Application.AddMessageFilter(this);
}
protected override void Dispose(bool disposing) {
if (disposing) Application.RemoveMessageFilter(this);
base.Dispose(disposing);
}
public bool PreFilterMessage(ref Message m) {
if (m.HWnd == this.Handle) {
if (m.Msg == 0x201) { // Trap WM_LBUTTONDOWN
Point pos = new Point(m.LParam.ToInt32());
// Do something with this, return true if the control shouldn't see it
//...
// return true
}
}
return false;
}
}
I needed the exact same functionality today, so this is tested and works:
1: Create a subclasser which can snatch your mouseclick:
internal class MessageSnatcher : NativeWindow
{
public event EventHandler LeftMouseClickOccured = delegate{};
private const int WM_LBUTTONDOWN = 0x201;
private const int WM_PARENTNOTIFY = 0x210;
private readonly Control _control;
public MessageSnatcher(Control control)
{
if (control.Handle != IntPtr.Zero)
AssignHandle(control.Handle);
else
control.HandleCreated += OnHandleCreated;
control.HandleDestroyed += OnHandleDestroyed;
_control = control;
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_PARENTNOTIFY)
{
if (m.WParam.ToInt64() == WM_LBUTTONDOWN)
LeftMouseClickOccured(this, EventArgs.Empty);
}
base.WndProc(ref m);
}
private void OnHandleCreated(object sender, EventArgs e)
{
AssignHandle(_control.Handle);
}
private void OnHandleDestroyed(object sender, EventArgs e)
{
ReleaseHandle();
}
}
2: Initialize the snatcher to hook into the panel WndProc:
private MessageSnatcher _snatcher;
public Form1()
{
InitializeComponent();
_snatcher = new MessageSnatcher(this.panel1);
}
3: The Message snatcher will get the WM_PARENTNOTIFY if you click on a child control.
You used to be able to override the OnBubbleEvent method on the control. In WPF the mechanism is called Routed events : http://weblogs.asp.net/vblasberg/archive/2010/03/30/wpf-routed-events-bubbling-several-layers-up.aspx
Bit late to the party, But What I did was to map all the click event of all the controls inside of the panel to panel click event. I know its nasty approach. but hey!!
a simple solution:
every control inside the usercontrol gets the same click-event "ControlClick". The usercontrol event click works with any controls inside.
private void ControlClick(Object sender, EventArgs e)
{
if (sender is UC_Vorgang uC_vorgang)
{
uC_vorgang.OnClick(e);
}
else
{
ControlClick(((Control)sender).Parent, e);
}
}
I know that I can get the current state by WindowState, but I want to know if there's any event that will fire up when the user tries to minimize the form.
You can use the Resize event and check the Forms.WindowState Property in the event.
private void Form1_Resize ( object sender , EventArgs e )
{
if ( WindowState == FormWindowState.Minimized )
{
// Do some stuff
}
}
To get in before the form has been minimised you'll have to hook into the WndProc procedure:
private const int WM_SYSCOMMAND = 0x0112;
private const int SC_MINIMIZE = 0xF020;
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
protected override void WndProc(ref Message m)
{
switch(m.Msg)
{
case WM_SYSCOMMAND:
int command = m.WParam.ToInt32() & 0xfff0;
if (command == SC_MINIMIZE)
{
// Do your action
}
// If you don't want to do the default action then break
break;
}
base.WndProc(ref m);
}
To react after the form has been minimised hook into the Resize event as the other answers point out (included here for completeness):
private void Form1_Resize (object sender, EventArgs e)
{
if (WindowState == FormWindowState.Minimized)
{
// Do your action
}
}
I don't know of a specific event, but the Resize event fires when the form is minimized, you can check for FormWindowState.Minimized in that event
For people who search for WPF windows minimizing event :
It's a bit different. For the callback use WindowState :
private void Form1_Resize(object sender, EventArgs e)
{
if (WindowState == FormWindowState.Minimized)
{
// Do some stuff
}
}
The event to use is StateChanged (instead Resize):
public Main()
{
InitializeComponent();
this.StateChanged += Form1_Resize;
}