This question is the result of my previous question DataGrid - grid selection is reset when new Data is arrived
=====================================================================
I have such DataGrid
<DataGrid AutoGenerateColumns="True" HorizontalAlignment="Stretch" Name="dataGrid1" VerticalAlignment="Stretch" ItemsSource="{Binding DataList}" IsReadOnly="True"/>
In my ViewModel I have such field:
public ObservableCollection<ConsoleData> DataList { get; set; }
And such method which is called every second:
private void model_DataArrived(List<ConsoleData> dataList)
{
DataList.Clear();
dataList.ForEach(x => DataList.Add(x));
}
=====================================================================
We have figured out that because I call DataList.Clear the selection in the UI control is cleared as well and I don't want this to happen. So likely I should not replace instances of ConsoleData in ViewModel, instead of that I should update these instances.
But ObservableCollection observes for add/remove I guess and doesn't observe for update isn't? So if I will update instances DataBinding will not work?
Another problem with the current application is that dataList.ForEach(x => DataList.Add(x)); forces databinding to execute on each iteration instead of executing only at the end?
Overall what is the right way to do what I want to do because current application doesn't work and has too many problems...
It's not clear how you are planning on updating an item in your ObservableCollection. There are at least two ways to do this. One way is to update all the properties that are changed in a ConsoleData object. In this case, you would have ConsoleData implement INotifyPropertyChanged. Another way is a direct update of item in the ObservableCollection. To do this, you could use the SetItem method of the ObservableCollection. This will raise the CollectionChanged event as the MSDN documentation for SetItem indicates.
Since you have indicated that you are using MVVM, the generally accepted thing to do would be to make your ObservableCollection be a collection of ConsoleDataViewModel instead of ConsoleData.
Another problem with the current application is that dataList.ForEach(x => DataList.Add(x)); forces databinding to execute on each iteration instead of executing only at the end?
I don't think you will have the refresh problem if you modify your model_DataArrived method to update instead of clear/add as indicated above.
The problem is the ObservableCollection not notifying when an item is changed; it notifies only when items are added and removed, as you say. To solve this problem I created a class I call VeryObservableCollection. For each object added, it hooks the object's NotifyPropertyChanged event to a handler that triggers a CollectionChanged event. For each object removed, it removes the handler. Very simple and should solve your issue. You just need to make sure you implement NotifyPropertyChanged on the objects held in the collection. For example:
public class VeryObservableCollection<T> : ObservableCollection<T>
/// <summary>
/// Override for setting item
/// </summary>
/// <param name="index">Index</param>
/// <param name="item">Item</param>
protected override void SetItem(int index, T item)
{
try
{
INotifyPropertyChanged propOld = Items[index] as INotifyPropertyChanged;
if (propOld != null)
propOld.PropertyChanged -= new PropertyChangedEventHandler(Affecting_PropertyChanged);
}
catch (Exception ex)
{
Exception ex2 = ex.InnerException;
}
INotifyPropertyChanged propNew = item as INotifyPropertyChanged;
if (propNew != null)
propNew.PropertyChanged += new PropertyChangedEventHandler(Affecting_PropertyChanged);
base.SetItem(index, item);
}
I think you can do something like this
private ObservableCollection<ConsoleData> dataList;
public ObservableCollection<ConsoleData> DataList
{
get {return dataList; }
set {dataList = value;}
}
And your data manipulations access only the field dataList. One time fiished manipulation force DataBinding to update, or reassign it, forcing in this way Biding to raise notification to WPF.
Should work.
if your ConsoleData implements INotifyPropertyChanged you can simply update these and you will see the changes in your ui - without selection lost.
OK first we need to understand that an item which was the source of selection on any Selector controls such as ListBox, DataGrid etc., if removed, will loose the selection.
For this please use a bulk adding and single notifying FastObservableCollection.
But to mainain such selection, you will have to set the selected item / value back to the DataGrid once Clear and AddRange takes place.
Related
Sadly, this might need a little background, so my apologies up front for the loquacity; here's what I have first: I have created a "MapCanvas" WPF control derived from Canvas that allows the user to draw and manipulate shapes (which it creates and manages as a list of Path instances). Users can draw, (multi-)select, rotate, scale, group, translate and clone such shapes, which display over an Image background hosted in the Canvas-derived control. The idea is to allow hotspots of interest to be drawn over the image of a map, with customisable data attached to their Tag properties. Everything can be translated or scaled (both the map Image and all the shapes drawn over it) using separate cached TranslateTransform, RotateTransform and ScaleTransform instances applied in a group to the Canvas' RenderTransform, so all the shapes and the underlying background image transform together. This all works fine.
What I want to do is to then move this MapCanvas control into another larger ongoing MVVM project as a View, and bind a ViewModel to it. Since the control has some fairly complex internal state to manage all the shape drawing and manipulation (and the usual fun and games with feeding keyboard and mouse events back from the host window to the canvas-derived control since Canvas is picky about when it will respond to them) there is quite a bit of code-behind in the control.
Ideally, yes, I know, that logic would live in the ViewModel and talk to the View through binding, but the Canvas needs to know about key, left & middle mouse up and down events as well as mouse movement to create and manipulate the Paths, and just getting those events to the control is not straightforward; I'm not in love with the code-behind solution, but it works. If anyone has a better idea, I'm all ears.
Anyway, I do at least want the Canvas' list of those Paths to be available to the ViewModel so that everything else in the larger project can be done with them properly through MVVM. Not too hard, I thought; expose the list of paths as a DependencyProperty. Since most of the changes involve simply adding to that list (that is, accessing but not assigning the list) I factored out methods for AddNewPathToList() and ClearPathList() which implement INotifyPropertyChanged so that the ViewModel will be advised if a path is added etc.
Then just bind a public property on the ViewModel back to the DependencyProperty on the View, right?
The code for the View (the MapCanvas control) is pretty long, so I won't post it. BTW if anyone is interested, I am happy to share that code; it works just fine, but is implemented with code-behind like an olde-fashioned Windows Forms control. I know that I am condemning my soul to the eternal fires for this. C'est la vie.
Control/View embedding in the main window:
<Window.DataContext>
<local:MapCanvasVM/>
</Window.DataContext>
<Grid>
<TextBlock Text="{Binding Path=ViewModelPathSelectionInfo}" Height="20" VerticalAlignment="Top"/>
<local:MapCanvas x:Name="mpcControl" ClipToBounds="True" Background="Gray" Margin="0,20,0,0" MultiSelectPath="{Binding Path=SelectedPaths, Mode=OneWayToSource}" NumSelectedPaths="{Binding Path=NumSelected, Mode=OneWayToSource}"/>
</Grid>
The TextBlock is just there for debugging; I can watch this in the View to see how many paths the ViewModel thinks there are selected.
The bindings are OneWayToSource since the View only ever tells the ViewModel what paths it currently has selected, never the other way; the paths are created and manipulated directly by the MapCanvas in response to mouse and key events. I know this isn't proper MVVM, but it was just too hard to even think about how to do all that. Instead, I just encapsulated everything into the control; you can drop it into a window and it works. To be honest, I've never tried MVVM in this direction (purely View to ViewModel) before, and I'm not even convinced it is the right way to go. But the project I want to drop this into will use these annotated maps as part of a much larger set of data already managed through MVVM.
Here are the DependencyProperties and modifier methods in the MapCanvas View:
public static readonly DependencyProperty MultiSelectPathProperty = DependencyProperty.Register("MultiSelectPath", typeof(List<Path>), typeof(MapCanvas));
public static readonly DependencyProperty NumSelectedPathsProperty = DependencyProperty.Register("NumSelectedPaths", typeof(int), typeof(MapCanvas));
and
protected void OnPropertyChanged(string PropertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
/// <summary>
/// Provides backing access to the MultiSelectPathProperty DependencyProperty exposing the list of currently selected Path instances; raises PropertyChanged for assignment but not changes through access; all modifying access should be made through AddToMultiSelectPath() or ClearMultiSelectPath() which also raise PropertyChanged once changes are made.
/// </summary>
public List<Path> MultiSelectPath
{
get
{
return (List<Path>)GetValue(MultiSelectPathProperty);
}
set
{
if (MultiSelectPath != value)
{
SetValue(MultiSelectPathProperty,value);
OnPropertyChanged("MultiSelectPath");
}
}
}
/// <summary>
/// Provides backing access to the NumSelectedPathsProperty DependencyProperty exposing the number of currently selected Path instances
/// </summary>
public int NumSelectedPaths
{
get { return (int)GetValue(NumSelectedPathsProperty); }
set
{
if (NumSelectedPaths != value)
{
SetValue(NumSelectedPathsProperty, value);
OnPropertyChanged("NumSelectedPaths");
}
}
}
/// <summary>
/// Adds a path to the multiselection and raises PropertyChanged to notify observers of that property that it has changed through access (normally only assignment triggers the event)
/// </summary>
/// <param name="NewPath"></param>
private void AddToMultiSelectPath(Path NewPath)
{
MultiSelectPath.Add(NewPath);
OnPropertyChanged("MultiSelectPath");
NumSelectedPaths = MultiSelectPath.Count;
}
/// <summary>
/// Clears the multiselection and raises PropertyChanged to notify observers of that property that it has changed through access (normally only assignment triggers the event)
/// </summary>
private void ClearMultiSelectPath()
{
MultiSelectPath.Clear();
OnPropertyChanged("MultiSelectPath");
NumSelectedPaths = MultiSelectPath.Count;
}
Updating the number of paths in the selection in the AddToMultiSelectPath(Path NewPath) or ClearMultiSelectPath() methods through the NumSelectedPaths setter raises the appropriate PropertyChanged event there, too, as you'd expect.
So far, so good.
Now here is the ViewModel, and in the comments you'll see why I'm so dumbfounded:
internal class MapCanvasVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private List<Path> mSelectedPaths = new List<Path>();
private int mNumSelected;
public MapCanvasVM()
{ }
private void OnPropertyChanged(string PropertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
public List<Path> SelectedPaths
{
get { return mSelectedPaths; }
set
{
if (mSelectedPaths != value)
{
mSelectedPaths = value;
// Huh ?!
// a breakpoint in this setter fires twice with new list (MapCanvas initialiser) then NULL (from InitializeComponent()?),
// but then with an empty list for the first time a path is added to the selection, then never again.
// BUT THE UNDERLYING PROPERTY IS CORRECTLY SET AT ALL TIMES.
// how is that even possible???
}
}
}
/// <summary>
/// Bound from VM back to a TextBlock on the View for debugging
/// </summary>
public string ViewModelPathSelectionInfo
{
get { return SelectedPaths.Count.ToString(); }
}
/// <summary>
/// Bound from View to VM
/// </summary>
public int NumSelected
{
get { return mNumSelected; }
set
{
if (mNumSelected != value)
{
mNumSelected = value;
// we don't actually care about this value, but knowing that it has changed means we know that the
// SelectedPaths property has also been updated, since for some reason that setter isn't behaving itself.
OnPropertyChanged("ViewModelPathSelectionInfo"); // let the View know we want to update the count of paths in the debugging TextBlock
var Dummy = SelectedPaths; // What??? SelectedPaths is CORRECTLY SET if I break here!!!
}
}
}
}
Well, no. That doesn't work, but its doesn't work in the strangest way, as noted in the comments above. Well, actually, it does seem to work, but I don't trust it an inch.
You can see that to test the bindings, I created an integer-valued NumSelectedPathsProperty DependencyProperty on the View that simply exposed the number of selected paths in the control's list (which is itself the other List-valued MultiSelectPathProperty DependencyProperty). When I bound NumSelectedPaths to the NumSelected property in the ViewModel using
NumSelectedPaths="{Binding Path=NumSelected, Mode=OneWayToSource}"
it works perfectly.
So why can I pass an integer from the View to the ViewModel but virtually identical code to pass a List using exactly the same setup has such weird behaviour?
I mean, it works, but stepping the code when I add a path to the selection in the MapCanvas only ever trips the setter once (with the wrong value) and never breaks again, even though breaking on the OTHER bound property and examining the runtime value of SelectedPaths shows it has the correct value every time, even though a breakpoint on the setter doesn't get hit.
So, finally, the question:
a) has anyone ever seen anything like this, or have any idea what is going on with the bindings behaving like that?
b) surely there is a better way to do this MVVM binding between the control and the ViewModel, yes? Advice?
c) in general, to get state from the View back to the ViewModel (not the other way, as virtually all the tutorials, code snippets and articles I've found explain) is there a general approach that is different to and better than what I've used here?
Thank you all so much in advance for reading all that, and for any kind words of advice.
I work on a WPF project build in MVVM architecture.
My ViewModel class contains ObservableCollection of SentenceVM class which is also a ViewModel class (implements INotifyPropertyChanged).
This ObservableCollection bound to a DataGrid and I would like to allow adding new records to the collection via the DataGrid build in function.
ObservableCollection<SentenceVM> observe = new ObservableCollection<SentenceVM>()
The problem is that the CollectionChanged event fires at the beginning of the adding row procedure. Thus, I don't know when to refer to the database to submit the new data.
I need this event to be fired when the user has finished adding the new data, not at the beginning.
I know it could be done by generating a command which executes on END inserting or on Enter key but i'm looking for a practical way to do so with this ObservableCollection.
Thank you !
I found out there is no way to do so via ObservableCollection. The only way to do so in practical way is to define an 'EventToCommand' trigger which executes on CellEditEnding event and execute the bounded command, like that:
1) Define the trigger class which inherits from the TriggerAction class and define the invoke method:
public class EventToCommandTrigger : TriggerAction<FrameworkElement>
2) Define the ICommand (dependency property) to bound to:
public ICommand CommandToExecute
{
get { return (ICommand)GetValue(CommandToExecuteProperty); }
set { SetValue(CommandToExecuteProperty, value); }
}
public static readonly DependencyProperty CommandToExecuteProperty =
DependencyProperty.Register("CommandToExecute", typeof(ICommand), typeof(EventToCommandTrigger), new FrameworkPropertyMetadata(null));
3) Implement the abstract Invoke method like that:
protected override void Invoke(object parameter)
{
if (CommandToExecute == null)
return;
if (CommandToExecute.CanExecute(parameter))
CommandToExecute.Execute(parameter);
}
4) Then in the xaml code, connect the CellEditEnding event to the above trigger, like that:
<i:Interaction.Triggers>
<i:EventTrigger EventName="CellEditEnding">
<triggers:EventToCommandTrigger CommandToExecute="{Binding Path=DeleteCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
The problem is that the CollectionChanged event fires at the beginning of the adding row procedure. Thus, I don't know when to refer to the database to submit the new data.
In my opinion, there's nothing wrong with inserting the data into the database inside the CollectionChanged event. In fact, the NotifyCollectionChangedEventArgs gives you everything you need in order to perform this action.
Consider looking at e.NewItems, and e.OldItems. The NewItems is the data that is being inserted into the collection, you can simply iterate through this list and act accordingly.
Thanks to the advice of tencntraze this question has been solved.
See the bottom of this question and tencntraze's answer for the solution.
I have a rather strange bug in my program that I'm currently wrestling with.
I'm developing an MVVM application, and I have a simple ListBox in my View with its ItemsSource bound to an ObservableCollection of strings. Whenever I add an item to this collection, it /does/ fire an event to notify that the collection has changed, yet this isn't reflected on the GUI during run-time unless I attempt to manually resize the window as the program is running, almost as if a resizing the window forces the GUI to refresh.
The XAML for the relevant control is as follows
<ListBox ItemsSource="{Binding Path=LogWindow}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="0,0,0,0" />
Which as you can see is perfectly normal. In my ViewModel for this view, I have a public property called LogWindow defined as follows
public ObservableCollection<string> LogWindow
{
get
{
return _logWindow;
}
set
{
_logWindow = value;
OnPropertyChanged("LogWindow");
}
}
The private member for this is as follows
private ObservableCollection<string> _logWindow;
In the ViewModel constructor I initialize _logWindow and wire up LogWindow CollectionChanged event to a handler which in turn fires a PropertyChanged notification.
My code for this is as follows
public MainWindowViewModel(IDialogService dialogService)
{
... skipped for brevity
LogWindow = new ObservableCollection<string>();
LogWindow.CollectionChanged += LogWindow_CollectionChanged;
...
}
The CollectionChanged event handler is as follows
void LogWindow_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged("LogWindow");
}
And finally when my View receives notification that it has received a new message from the Model, I add a new string to the observable collection as follows
LogWindow.Add(_vehicleWeightManager.SerialMessage);
When LogWindow.Add happens, my event listener fires and my breakpoint is hit to confirm the code gets that far as illustrated below
After this my code calls the OnPropertyChanged function which is inherited from my ViewModelBase, this functions fine for my other GUI elements such as labels and the like, so that almost certainly isn't the issue.
I applied a watch to LogWindow to keep track that the collection was indeed being added to, and it is as illustrated below
So at this stage I'm at a loss. My GUI will only update if I resize the window in some way, otherwise there is no visual update. The data is present in the bound property, and afaik the GUI is alerted when a new item is added to the property... Does anyone have any insight that may help?
Thanks to the advice of tencntraze and kallocain this has been solved.
The problem was caused by another thread attempting to add to the LogWindow collection which was causing erroneous behaviour. Rather than use Dispatcher as suggested, I used a TaskFactory code as follows.
TaskFactory uiFactory;
...
...
public MainWindowViewModel(IDialogService dialogService)
{
...
_logWindow = new ObservableCollection<string>();
uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
}
...
...
void _vehicleWeightManager_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
...
if (e.PropertyName == "VehicleWeight")
{
// Marshal the SerialMessage property to the GUI thread.
uiFactory.StartNew(() => LogWindow.Add(_vehicleWeightManager.SerialMessage.Replace("\n", ""))).Wait();
}
}
Further reading for the above method can be found here
If you're receiving the message on another thread, you'll need to invoke it back to the UI thread, otherwise there can be unpredictable behavior. Sometimes this is an exception, other times it works out to do nothing.
this.Dispatcher.BeginInvoke(new Action(() =>
{
LogWindow.Add(_vehicleWeightManager.SerialMessage);
}));
I am creating a small Windows Form application that will receive messages from a background thread and then add them to a List on the Main form. On the form I have a DataGridView with the DataSource set to the message list (e.g. List<Message>)
At the moment, messages are piling up but I can't get the DataGridView to render the messages as they arise. Here is a summary of the approach I have used.
1. Initialize the main form setting the binding and startnig the message routines
internal Main(IMessageDispatcher messageDispatcher, IMessagePublisher messagePublisher)
{
InitializeComponent();
_messageEntries = new List<Message>();
BindToMessageEntriesList();
_messageDispatcher = messageDispatcher;
_messageDispatcher.OnMessageReceived += MessageDispatcher_OnMessageReceived;
_messageDispatcher.Start();
}
private void BindToMessageEntriesList()
{
MessageEntriesGrid.DataSource = _messageEntries;
}
2. Process received messages and add to the internal list
private void MessageDispatcher_OnMessageReceived(Message message)
{
lock (_logEntries)
{
_messageEntries.Add(message);
}
var cb = new RefreshListCallbackDelegate(RefreshView);
this.Invoke(cb);
}
3. Refresh on the main thread
private void RefreshView()
{
MessageEntriesGrid.Refresh();
}
Can anyone suggest how I can get the UI to reflect the changes as they occure.
Note: I have also tried using the Invalidate method and the ResetBindings method. I also tried switching the List to an ObservableCollection
You can replace the List<Message>() to a custom class and inherits of BindingList<T> and attach your Message Dispatcher into it.
Example
public class MessageList : BindingList<Message>
{
}
in your main form
subsribe the ListChanged event and you good to go.
DataGridView won't update its display automatically when you bind a List to it and then modify the original List. It will only display the data that existed in the list at the time you bound it.
If you want the DataGridView to dynamically update when the bound collection changes, you need to bind a collection that implements IBindingList (like BindingList http://msdn.microsoft.com/en-us/library/ms132679(v=vs.90).aspx) or IBindingListView (like BindingSource http://msdn.microsoft.com/en-us/library/system.windows.forms.bindingsource(v=vs.90).aspx).
When Ever I do anything with datagridview like this I use the following system :-
I have a DataObject, In your case a List object.
I then create a Binding Object and set its datasource as the DataObject.
I the set the DatagridView DataSource as the Binding Object.
That way, when your making changes to The Data Object, they filter through to the Datgrid Automatically.
In your case, you should do as Turbot Says, create a class that inherits the BindingList object, and you are good to go.
Try using a BindingList instead of List. It forms a two-way connection. (It's in the ComponentModel namespace)
I have a WinForms ListView, obviously containing ListViewItems. I'd like to be able to attach a click event to each item, instead of to the entire ListView (and then trying to figure out what item was clicked). The reason for this is that I need to perform a different action based on which item was selected. The ListViewItem class seems to be very limited in this regard. Is there any way to do what I want, or am I forced to use the ListView.Click event?
I would still use the ListView Click event.
A trick I've used in these situations is to use the Tag property of a ListViewItem. It's great for storing per item data and you can put anything in it.
It may make sense to subclass ListViewItem and use virtual dispatch to select the appropriate behavior based on the selected ListViewItem in the appropriate ListView event.
E.g. (uncompiled)
public abstract class MyItems : ListViewItem
{
public abstract DoOperation();
}
public class MyItemA : MyItems
{
public override DoOperation()
{ /* whatever a */ }
}
public class MyItemB : MyItems
{
public override DoOperation()
{ /* whatever b */ }
}
// in ListView event
MyItems item = (MyItems)this.SelectedItem;
item.DoOperation();
As others have mentioned, it may also make sense to use the appropriate Tag property. Which technique you go for really depends on what your action is (and therefore where it belongs, architecturally). I assumed the subclass made more sense because you're looking for a click on a listview item, and that (to me) seems more likely to be presentation-layer b/c you're overriding some standard control behavior (which would normally just select an item) as opposed to doing something in response to behavior.
In most use cases, a ListViewItem is a representation in the UI of some object, and what you're trying to do is execute a method of the object that the ListViewItem represents when the user clicks on it. For the sake of simplicity and maintainability, you want as few things to sit between the user's mouse-click and the actual method being executed.
You can store the object in the ListViewItem's Tag property and then reference it in the Click event handler, but that results in code that's got some inherent weak points:
private void MyListView_Click(object sender, EventArgs e)
{
ListView l = (ListView)sender;
if (l.SelectedItem != null)
{
MyClass obj = l.SelectedItem.Tag as MyClass;
if (obj != null)
{
obj.Method();
}
}
}
That's a lot of casting and null-reference checking. And the really weak thing about this code is that if it turns out that Tag is null, or contains something other than a MyClass object, you don't really know where to look to find out where the problem is occurring.
Contrast it with code like this:
private void MyListView_Click(object sender, EventArgs e)
{
MyClass.ListViewClicked(sender as ListView);
}
When you're maintaining this code, you don't know how that ListViewClicked method is implemented, but at least you know where to look for it - in MyClass. And when you do, you'll see something like this:
public static void ListViewClicked(ListView listView)
{
if (listView.SelectedItem == null)
{
return;
}
if (ListViewItemLookup.ContainsKey(listView.SelectedItem))
{
ListViewItemLookup[listView.SelectedItem].Execute();
}
}
Well, that's interesting. Following the thread, how does that dictionary get populated? You find that in another method in MyClass:
private static Dictionary<ListViewItem, MyClass> ListViewItemLookup =
new Dictionary<ListViewItem, MyClass>();
public ListViewItem GetListViewItem()
{
ListViewItem item = new ListViewItem();
item.Text = SomeProperty;
// population of other ListViewItem columns goes here
ListViewItemLookup.Add(item, this);
return item;
}
(Reasonable people can disagree about whether or not it's appropriate for a class to be so closely tied to a specific form of its representation in the UI - there are those who would isolate these methods and this dictionary in a helper class instead of in MyClass itself, and depending on how hairy the rest of the problem is I might do it too.)
This approach solves a number of problems: it gives you a simple way of handling the ListView's Click event properly, which is what you asked for. But it also isolates the not-always-trivial process of creating the ListViewItem in the first place. It reduces the amount of code you'll have to move around if you refactor your form and move the ListView to another form. And it reduces the number of things that your form class needs to know about, which is generally a good thing.
Also, it's testable. Generally, the only way to test code in a UI event handler is through the UI. This approach lets you isolate all of the logic surrounding this part of the UI in something that you can unit test; the only thing you can't write a unit test for is a single line of code in the form.
I should point out that the other approach people have been suggesting - subclassing ListViewItem - is perfectly fine too. You put the logic I put in the GetListViewItem method in the class's constructor, make the MyClass instance a private property of the class, and expose a Click method that calls the method of MyClass. Pretty much the only reason I don't like it is that it still leaves you with a fair amount of code in your form that you can't really unit test:
ListView l = (ListView)sender;
if (l.SelectedItem != null)
{
MyClassListViewItem item = l.SelectedItem as MyClassListViewItem;
if (item != null)
{
item.MyClass.Method();
}
}
You might however have luck sticking a reference to a delegate or other handler in the tag field (assuming there is a tag property of a ListViewItem). You would still have to determine which ListViewItem is clicked, but you could then go straight to the tag instead of another decision structure.
You want to create a new class (or classes if there are various types), which inherits from ListViewItem, then populate your ListView with these objects (as long as they inherit from listview (even several levels of inheritence) The ListView control will take them).
Then add a click method to your custom class(es) and on the ItemClick event of your listView, just call the click method of the clicked item. (some casting may be needed)
Actually there is no way to use a ListViewItem. You have to use the ListView itself. By using the 'SelectedItems' property of the ListView you can access the selected ListViewItems.
One option is to override the ListViewItem class an implement the specific stuff in there. Then you can cast the selected item to the overridden one and perform the action.
I really don't understand the reason to do so instead of just using the regular ListView Click event, but if I were to do like you suggest I would assign an EventHandler delegate to the Tag property of each ListViewItem, then in the ListView Click event handler I would check if the ListViewItem.Tag <> null, and if so call the delegate.