Winforms ListView TopItem changed event? - c#

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();
}
};
}

Related

how to call Resize Event in C#

I'm trying to create new Button with custom event. It's new for me. I'm trying to call "Resize". I wanna create "switch" like in android.
I'm trying to do this like other existing controls. I've been doing this for 2 days and i still have nothing. I belive that you will able to help me :)
Here is my code:
public abstract class SwitchBase : Control
{
private Button first;
private Button second;
public SwitchBase()
{
InitializeMySwitch();
}
private void InitializeMySwitch()
{
Controls.Add(first = new Button());
Controls.Add(second = new Button());
//first
first.Text = "first";
//second
second.Text = "second";
second.Location = new System.Drawing.Point(first.Location.X + first.Width, first.Location.Y);
}
public delegate void ChangedEventHandler(object source, EventArgs args);
public event ChangedEventHandler Changed;
protected virtual void OnSwitchChanged()
{
if (Changed != null)
Changed(this, EventArgs.Empty);
}
public delegate void ResizeEventHandler(object source, EventArgs args);
public event ResizeEventHandler Resize;
protected virtual void OnResize()
{
Resize(this, EventArgs.Empty);
}
}
public class Switch : SwitchBase
{
public Switch()
{
}
protected override void OnSwitchChanged()
{
base.OnSwitchChanged();
}
protected override void OnResize()
{
base.OnResize();
}
}
In another button I change the size of my switch
From reading your code, I gather that by "call Resize" you mean to raise the event. What you are doing is correct... although it should be noted that by the default event implementation, it will be null if there are no subscribers...
In fact, another thread could be unsubscribing behind your back. Because of that the advice is to take a copy.
You can do that as follows:
var resize = Resize;
if (resize != null)
{
resize(this, EventArgs.Empty)
}
It should be noted that the above code will call the subscribers to the event, but will not cause the cotrol to resize. If what you want is to change the size of your control, then do that:
this.Size = new Size(400, 200);
Or:
this.Width = 400;
this.Height = 200;
Note: I don't know what Control class you are using. In particular, if it were System.Windows.Forms.Control it already has a Resize event, and thus you won't be defining your own. Chances are you are using a Control class that doesn't even have Size or Width and Height.
Edit: System.Web.UI.Control doesn't have Resize, nor Size or Width and Height. But System.Windows.Controls.Control has Width and Height even thought it doesn't have Resize.

How to handle the MouseMoveEvent in the underlying Form instead of the Panels inside it?

I have an application written in C#, It is a directory system that will display information in a slideshow fashion.
In my Form I have a Panel docked to fill the form's content. Inside that panel there are 9 panels where each one displays the information of a particular object.
Now what I want is that whenever I move the mouse I want to trigger the MouseMoveEvent of the form hosting the panel, instead of those of the big panel or the panels inside it.
Here is my code handling the form's MouseMoveEvent:
protected override void OnMouseMove(MouseEventArgs e)
{
MessageBox.Show("Moved!");
}
I know that this will not fire because the mouse cursor is inside the panel but how to trigger the event on the form anyway?
The purpose of this is to hide the current form and show another form when mouse cursor inside the form moved. It is possible?
This example works correct for me, program call TheMouseMoved() method only if I move mouse.
public partial class Form1 : Form
{
int counter = 0;
public Form1()
{
GlobalMouseHandler gmh = new GlobalMouseHandler();
gmh.TheMouseMoved += new MouseMovedEvent(gmh_TheMouseMoved);
Application.AddMessageFilter(gmh);
InitializeComponent();
}
void gmh_TheMouseMoved()
{
Point cur_pos = System.Windows.Forms.Cursor.Position;
//System.Console.WriteLine(cur_pos);
System.Console.WriteLine("{0}. [ {1},{2} ]", counter++, (cur_pos.X - this.Location.X), (cur_pos.Y - this.Location.Y));
}
}
public delegate void MouseMovedEvent();
public class GlobalMouseHandler : IMessageFilter
{
private const int WM_MOUSEMOVE = 0x0200;
public event MouseMovedEvent TheMouseMoved;
#region IMessageFilter Members
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == WM_MOUSEMOVE)
{
if (TheMouseMoved != null)
{
TheMouseMoved();
}
}
// Always allow message to continue to the next filter control
return false;
}
#endregion
}
Try the "MouseMove" Event on the panel. If you disable the docked panel, the "MouseMove" Event of the Forms will be triggered.
I solve the problem by modifying the answer from How do I capture the mouse move event because the accepted answer is continuously firing even though the mouse is not moving according to #Randy Gamage comment.
I solved it using this code.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace GlobalMouseEvents
{
public partial class Form1 : Form
{
public Form1()
{
GlobalMouseHandler gmh = new GlobalMouseHandler();
gmh.TheMouseMoved += new MouseMovedEvent(gmh_TheMouseMoved);
Application.AddMessageFilter(gmh);
InitializeComponent();
}
void gmh_TheMouseMoved()
{
Point cur_pos = System.Windows.Forms.Cursor.Position;
System.Console.WriteLine(cur_pos);
}
}
public delegate void MouseMovedEvent();
public class GlobalMouseHandler : IMessageFilter
{
private const int WM_MOUSEMOVE = 0x0200;
private System.Drawing.Point previousMousePosition = new System.Drawing.Point();
public static event EventHandler<MouseEventArgs> MouseMovedEvent = delegate { };
#region IMessageFilter Members
public bool PreFilterMessage(ref System.Windows.Forms.Message m)
{
if (m.Msg == WM_MOUSEMOVE)
{
System.Drawing.Point currentMousePoint = Control.MousePosition;
// Prevent event from firing twice.
if (previousMousePosition == new System.Drawing.Point(0, 0))
{ return; }
if (previousMousePosition != currentMousePoint)
{
previousMousePosition = currentMousePoint;
MouseMovedEvent(this, new MouseEventArgs(MouseButtons.None, 0, currentMousePoint.X, currentMousePoint.Y, 0));
}
}
// Always allow message to continue to the next filter control
return false;
}
#endregion
}
}

Determine when LongListSelector is Scrolling

I would just simply like to be able to minimize the application bar when I am scrolling down, and then show its normal size when scrolling up. I've seen this ability on the facebook app and it seems very appealing and user friendly. I have my LongListSelector with items bound to it, and an appbar already in code behind. What is the missing key to enable such a feature?
You just need to figure out when the user is scrolling and in what direction. Here's a great article with example code. Detecting WP8 LongListSelector’s end of scroll. You can modify it to the point where it does exactly what you want.
However, if I was going do it, I would take a more direct route. I would derived my own LLS and attach a property to the value of the scrollbar. Something like this :)
public class MyLLS : LongListSelector, INotifyPropertyChanged
{
// implement the INotify
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// dat grab doe
sb = this.GetTemplateChild("VerticalScrollBar") as System.Windows.Controls.Primitives.ScrollBar;
sb.ValueChanged += sb_ValueChanged;
}
void sb_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
// an animation has happen and they have moved a significance distance
// set the new value
ScrollValue = e.NewValue;
// determine scroll direction
if(e.NewValue > e.OldValue)
{
scroll_direction_down = true;
}
else
{
scroll_direction_down = false;
}
}
public System.Windows.Controls.Primitives.ScrollBar sb;
private bool scroll_direction_down = false; // or whatever default you want
public bool ScrollDirectionDown
{ get { return scroll_direction_down; } }
public double ScrollValue
{
get
{
if (sb != null)
{
return sb.Value;
}
else
return 0;
}
set
{
sb.Value = value;
NotifyPropertyChanged("ScrollValue");
}
}
}
Now you know the exact scroll position. You can even get the top and bottom value by doing
double min = this.sb.Minimum;
double max = this.sb.Maximum;
Now bind that ScrollDirectionDown property to a converter to your AppBar visibility and you'll have your goals met.
If you can't bind then you have to do a callback to update the visibility. But if you want something more simple just hook it up to the ManipulationStateChanged event of the custom LLS.
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
}
private void lls_ManipulationStateChanged(object sender, EventArgs e)
{
if (lls.ScrollDirectionDown)
{
ApplicationBar.IsVisible = false;
}
else
{
ApplicationBar.IsVisible = true;
}
}
}
So for that you have to detect when the longlistselector starts scrolling. For that to achieve there's a similar thread here:
Windows Phone 8 Long List Selector - scroll to bottom after data loaded async
In the DoAndScroll method you could simply include the code to minimize the AppBar.
Within your xaml code of your appbar, change the mode to Minimized.
<shell:ApplicationBar Mode="Minimized" Opacity="1.0" IsMenuEnabled="True" IsVisible="True"></>
Thereafter whenever it scrolls back up, make the Mode of the AppBarto Default.
Or else have a look at this to detect the bottom of the longlistselector.
Detecting WP8 LongListSelector’s end of scroll

How to pass class instances to WndProc in C#

How do i pass an instance of an object which is in my main form to WndProc method
For ex:
I have a ComboBox object - objCombo. And i have to capture a certain window message before the system draws the drop down list box.
One way to do this I can have a custom combobox which derives from the ComboBox
public class CustomComboBox : ComboBox
{
//... some initialization code goes here
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_CTLCOLORLISTBOX)
{
// capture the message and do some work.
//here i can get the reference to the CustomComboBox by using
//this keyword.
}
}
}
However, I want to know is there a way where in I can do this without having go through the process of creating the custom combobox and do exactly the same. i.e. capturing the Windows message inside my Form class using the reference to my combobox instance. ?
i.e.
public class MyForm : Form
{
//... some initialization code goes here including the InitializeComponent
// for form objects and other controls
private void CaptureComboWndProc(ref Message m)
{
// this method will capture only the windows message specific to objCombo ??
}
}
I hope I am being clear with the question
Thanks and Cheers
VATSA
Better way is to create a descendant of combobox. Which is very clear way of doing it.
However you can find the control from message, here's how you go.
Use Control.FromHandle to find the control to which message is posted.
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_CTLCOLORLISTBOX)
{
if(Control.FromHandle(m.HWnd) == this.objCombo)
{
CaptureComboWndProc(ref m);
}
}
}
private void CaptureComboWndProc(ref Message m)
{
}
and... Finally I'd like to say don't do this please.
It is possible, the generic technique is called "sub-classing the window" in Windows GUI programming. It works by replacing the window procedure of a window. This is already done for every native window control you find back in the toolbox (ListView, ComboBox, etc), that's how they raise events. And can be done repeatedly. No need to pinvoke, the NativeWindow class supports it with its AssignHandle() method.
You have to derive your own class from NativeWindow and override the WndProc() method to implement custom message handling. Use, say, the form's Load event to attach it. Roughly:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private class ComboHooker : NativeWindow {
protected override void WndProc(ref Message m) {
if (m.Msg == 0x134) {
// etc...
}
else {
// Stop sub-classing on WM_NCDESTROY
if (m.Msg == 0x82) this.ReleaseHandle();
base.WndProc(ref m);
}
}
}
private void hookComboBoxes(Control.ControlCollection ctls) {
foreach (Control ctl in ctls) {
if (ctl.GetType() == typeof(ComboBox)) {
new ComboHooker().AssignHandle(ctl.Handle);
}
hookComboBoxes(ctl.Controls);
}
}
protected override void OnLoad(EventArgs e) {
hookComboBoxes(this.Controls);
base.OnLoad(e);
}
}

Handling double click events on ListBox items in C#

I am trying to do something when double clicking an item in a ListBox. I have found this code for doing that
void listBox1_MouseDoubleClick(object sender, MouseEventArgs e)
{
int index = this.listBox1.IndexFromPoint(e.Location);
if (index != System.Windows.Forms.ListBox.NoMatches)
{
MessageBox.Show(index.ToString());
//do your stuff here
}
}
However, when i click on an item, the event isn't fired. The event is fired if i click in the ListBox below all the items.
I set the DataSource property of the ListBox to IList<MyObject>.
Any ideas?
Tried creating a form with a ListBox with MouseDown and DoubleClick events. As far as I can see, the only situation, when DoubleClick won't fire, is if inside the MouseDown you call the MessageBox.Show(...). In other cases it works fine.
And one more thing, I don't know for sure, if it is important, but anyway. Of course, you can get the index of the item like this:
int index = this.listBox1.IndexFromPoint(e.Location);
But this way is fine as well:
if (listBox1.SelectedItem != null)
...
Works for me, so I assume there might be something about the items in the list (custom? intercepting the event?) or the event is not properly wired up.
For example try this (complete Form1.cs):
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Forms;
namespace WindowsFormsApplication1 {
public class MyObject {
public MyObject(string someValue) {
SomeValue = someValue;
}
protected string SomeValue { get; set; }
public override string ToString() {
return SomeValue;
}
}
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
var list = new List<MyObject> {
new MyObject("Item one"), new MyObject("Item two")
};
listBox1.DataSource = list;
}
private void listBox1_DoubleClick(object sender, EventArgs e) {
Debug.WriteLine("DoubleClick event fired on ListBox");
}
}
}
With the designer source file (Form1.Designer.cs) containing this:
private void InitializeComponent() {
this.listBox1 = new System.Windows.Forms.ListBox();
... // left out for brevity
this.listBox1.DoubleClick += new System.EventHandler(this.listBox1_DoubleClick);
As a test, create a new Forms base application through the templates, then add just the ListBox and define a class MyObject. See whether you observe the same or a different behavior.
Thank you for all replies. It now works. I solved it, like 26071986 said, with handling double click in the MouseDown handler by checking if e.Clicks is 1. If so, I call DoDragDrop, if not, I call the method that handles double click.
private void MouseDown_handler(object sender, MouseEventArgs e)
{
var listBox = (ListBox) sender;
if (e.Clicks != 1)
{
DoubleClick_handler(listBox1.SelectedItem);
return;
}
var pt = new Point(e.X, e.Y);
int index = listBox.IndexFromPoint(pt);
// Starts a drag-and-drop operation with that item.
if (index >= 0)
{
var item = (listBox.Items[index] as MyObject).CommaDelimitedString();
listBox.DoDragDrop(item, DragDropEffects.Copy | DragDropEffects.Move);
}
}
Here's what I used in the MouseDoubleClick event.
private void YourMethodForDoubleClick(object sender, MouseButtonEventArgs e)
{
Type sourceType = e.OriginalSource.GetType();
if (sourceType != typeof(System.Windows.Controls.TextBlock)
&& sourceType != typeof(System.Windows.Controls.Border))
return;
//if you get here, it's one of the list items.
DoStuff();
...
}
John: then it works. But i figured out that the event isn't fired because I am also handling the MouseDown event. I tried to remove the MouseDown handling, and then it works. Is there a smooth way to handle both those events? If not, I just have to find some other way to catch a double click through the MouseDown event.

Categories