WPF ListBoxItem Events - wpf

One of those 'Why is this so hard?" questions.
I have a ListBox (containing details of share portfolios). The listbox item uses a grid to display attributes of the portfolio. Source is a list of portfolios in the View Model.
ListBox is multiselect - when selection changes, a list of the constituents of the selected portfolios is re-populated.
What I want to do is put a button (or menu or whatever) on the listboxitem to display a list of possible actions (Trade, Unitise, Delete etc).
When an action is selected I need to execute the action against the appropriate portfolio. Ideally I want the actions to be available for both selected and unselected items.
I can handle the event, but how do I detect which item (portfolio) the user selected? I've looked at GotFocus() but it doesn't seem to fire.
In other words if a control in a Listboxitem, fires an event, how does the event 'know' which ListBoxItem raised it?

For me, the solution here, seen as you mentioned MVVM, would be to have the ListBox populated by a collection of ViewModels, e.g., something like ObservableCollection<PortfolioViewModel>.
It would then just be a case of binding the Command property of the Button to an ICommand on the ViewModel that executes whatever work you need doing.

I can handle the event, but how do I detect which item (portfolio) the user selected? I've looked at GotFocus() but it doesn't seem to fire.
You could cast the DataContext of the clicked Button to the corresponding object in te ListBox, e.g.:
private void DeleteButton_Clicked(object sender, RoutedEventArgs e)
{
Button deleteButton = sender as Button;
var portfolio = deleteButton.DataContext as Portfolio; //or whatever your type is called
//access any members of the portfolio...
}

Related

How do I get the UI object inside of a content presenter?

I have an ItemsControl displaying a list via binding. The list is of a ViewModel type which is then referenced in a DataTemplate to display a button. The button has it's UID bound to a GUID from the the view model. When a new view model is added to the bound list, I need to get hold of the button that will be added, as the buttons need to be able to be dragged/dropped by the user. At the moment the closest I can get is finding the ContentPresenter that displays the button, but the content of that ContentPresenter is of type view model.
Is there a way to find the button that has been added? Or should I not used a DataTemplate and create the buttons my self in order to access them?
I have used the VisualTree helper to get the content presenter, but have not managed to find the button.
You could handle the Loaded event for the Button:
<DataTemplate>
<Button Loaded="OnButtonLoaded" ... />
private void OnButtonLoaded(object sender, RoutedEventArgs e)
{
Button button = (Button)sender;
//...
}
You won't be able to get a reference to it using the VisualTreeHelper until it has actually been added to the visual tree and loaded anyway.

Auto cancel changes in DataForm when selection is changed

I wonder how you do such thing. Assume, we have MVVM CRUD app which modifies a tree (menu structure, for example). We have a view model with the menu items and two views: the first with a TreeView and the second with a DataForm. Main problems are:
DataForm can not handle
hierarchical data.
Depending on the menu item selected
in the TreeView the DataForm
should display different set of
fields (for example for menu items
with children or without).
I've ended up with the following. View model has 3 fields:
Items — the collection of
MenuItem objects which have their
own Children collection for
building hierarchical data source.
SelectedItem — currently selected
MenuItem in the TreeView.
EditedItem — EditViewModel
object which basically has two
descendants: MenuItemEditViewModel
and LeafMenuItemEditViewModel.
This property is set automatically
when SelectedItem is changed. Its
actual type is inferred from the
SelectedItem.Children emptiness.
TreeView is bound to Items and SelectedItem. DataForm is not required to maintain currency in this case (instead current item is set by the TreeView) nor it is responsible for creating and deleting items. That's why I decided to bind only its CurrentItem to view model's EditedItem (ItemsSource is unbound). Its AutoCommit is set to False (when it is True and ItemsSource is unbound all current item changes get copied to newly selected item when you select different item in the TreeView, which is not so nice). DataForm fields are autogenerated.
Obviously, that now if we select an item in the TreeView, then make some changes in the DataForm and try to select different item in the TreeView we'll get well-known
Cannot change currency when an item
has validation errors or it is being
edited and AutoCommit is false. Set
ItemsSource to a ICollectionView to
manage currency instead
In this case I want DataForm to discard all changes implicitly. There is a workaround to call DataForm.CancelEdit() before TreeView selected item is changed (usually an event like PreviewSelectionChanged or BeforeSelectionChanged). But it is not the MVVM way since the TreeView and the DataForm are defined in completely different views (read: is not acceptable).
Is there something like AutoCancel which forces DataForm to cancel changes when its CurrentItem is changed? Maybe someone from dev team can answer? Or how would you deal with such problem?
I was surprised to find the Silverlight is severly lacking in this functionality, considering all the business oriented RIA functionality. AutoCommit is not acceptable to me because I want the user to explicitly acknowledge pending changes, rather than just commit something to the database that they may not want.
You can reliably track the edit mode of the DataForm using a private member variable and trapping the BeginningEdit and EditEnded events of the DataForm (naming inconsistency! Why one is called xxxEdit and the others are Editxxx is beyond me. Should it not be EditBeginning and EditEnded??). Inside the event handler for BeginningEdit, set the flag to true and set it to false in EditEnded.
In your SelectionChanged event, you can then check the flag. If it is true, you can call the CancelEdit on the DataForm.
private bool _editing = false;
public MainPage() {
DataForm1.BeinningEdit +=
new EventHandler<CancelEventArgs>(DataForm1_BeginningEdit);
DataForm1.EditEnded +=
new EventHandler<DataFormEditEndedEventArgs>(DataForm1_EditEnded);
}
protected void DataForm1_BeginningEdit(object sender,
System.ComponentModel.CancelEventArgs e) {
_editing = true;
}
protected void DataForm1_EditEnded(object sender,
DataFormEditEndedEventArgs e) {
_editing = false;
}
void TreeView1_SelectedItemChanged(object sender,
RoutedPropertyChangedEventArgs<object> e)
{
if (_editing) {
object previous = DataForm1.SelectedItem;
object current = TreeView1.SelectedItem;
if (MessageBox.Show("Are you sure you want to cancel the changes?",
"Confirm", MessageBoxbutton.OKCancel) == MessageBoxResult.OK) {
DataForm1.CancelEdit();
}
else {
TreeView1.SelectedItem = previous;
}
}
}
Have you tried to set AutoCommit at True ?

How do I hook an event to a control that comes in dynamically as part of a DataTemplate?

I have a ListBox that uses DataTemplateSelector to dynamically decide what
template to use based on the type of the item in the list. I now want to hook
the events that could be fired by controls within the DataTemplate. For example,
if one of the templates has a checkbox in it, I want the application using the
control to be notified when the checkbox is checked. If a different template has
a button within it, I want to be notified when the button is clicked.
Also, since its a ListBox, many of the items could have the same template. So
I will need some kind of RoutedEventArgs so I can walk up from OriginalSource to get
some context information to handle the event.
My solution was to use MouseLeftButtonUp. This works fine for TextBlocks, but it looks like CheckBox and Button controls set handled to true, so the event doesnt bubble up. How can I address these
events so I can assign handlers to them in my calling application?
(Also, Silverlight doesn't actually support DataTemplateSelector, so i followed this example to implement it)
If you are defining the templates in the Xaml for the user control where your event handlers are placed then you should simply be able to assign the event handlers in the Xaml.
However in the specific scenario you outline you can also listen for the MouseLeftButtonUp event via the AddHandler method:-
myListBox.AddHandler(UIElement.MouseLeftButtonUpEvent, myListBox_MouseLeftButtonUp, true);
...
private void myListBox_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
//e.OriginalSource available for your inspection
}
Note by using AddHandler and passing true in the third parameter you will get the event regardless of whether it has been handled.

WPF TabControl Switch behaviour

I have a tabcontrol which binds to an observable collection of tabs.
The tabcontrol always has the first tab which hosts a listview bound to another observable collection.
On selecting an item in that list view a new tab is created an focus given to it.
The problem I am having is:
When I switch back to the first tab there is a pause while it redraws / creates the listview items (contains images so slow)
The item selected before moving to the new tab is nolonger selected. Instead the listview is at the top with no item selected.
Can someone please explain to me how the tabcontrol operates is it really distroying the tab item content each time? and how I can instead have a behaviour where the item remains selected when I return to that tab?
Update:
I have confirmed by adding debug print messages to events that no events fire on this switch-back and forth but the first tab is being unloaded - more specifically the usercontrol hosted in that tab is??.
It sounds like the ObservableCollection is the culprit. If you are changing the collection items to control the display, then every time the collection changes won't it redraw the entire tab collection?
Instead, why not maintain the TabItem collection directly? You could then manage the Visibility property of the TabItems to display them or not.
First I needed to ensure my listview bound to my collection correctly i.e. the item stayed selected by adding the property:
IsSynchronizedWithCurrentItem="True"
I then added a loaded event handler to the listview so the item is scrolled into view on switching back:
private void ListView_Loaded(object sender, RoutedEventArgs e)
{
ICollectionView collectionView = CollectionViewSource.GetDefaultView(DataContext);
if (collectionView != null)
{
ItemControl.ScrollIntoView(collectionView.CurrentItem);
}
}

What's the best way to auto-scroll a list view to the last added item?

I use a ListView to show a list of errors as they occur in my application. It behaves and looks exactly like the Error List in Visual Studio. I want to add auto-scrolling when the last error item is selected (like how Visual Studio's Log Window auto-scrolls when you place the caret at the end).
The list of errors is in an ObservableCollection, which is passed to the ListView.ItemsSource like this:
public ObservableCollection<ErrorListItem> Items;
...
MyListView.ItemsSource = _Items;
I tried performing the auto-scroll in the _Items_CollectionChanged event handler, but because this is the event on the ItemsSource and not on the actual ListViewItems, it's a pain to figure out if the last item is selected, select the new row, etc. It's especially hard since it seems the ListViewItems are not created instantly. I managed to make it auto-scroll by delaying the call to set the last item selected like this:
void _Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// determine the last item to select from 'e'
...
_ItemPendingToBeScrolled = newItemToSelect;
ListView.SelectedItem = newItemToSelect;
Dispatcher.BeginInvoke(DispatcherPriority.Background,
(ThreadStart)delegate
{
if (_ItemPendingToBeScrolled != null)
{
ListView.ScrollIntoView(_ItemPendingToBeScrolled);
ItemPendingToBeScrolled = null;
}
})
}
But that's obviously not the right way to do it. Also, I want things to keep working if the list is filtered (not checking the last item in my source, but the last ListViewItem in the ListView).
Is there a way to listen to events when a ListViewItem gets added to the ListView following an addition to the bound collection? That would be the ideal event to capture in order to properly do my auto-scrolling. Or is there another technique I could use?
I have a lot of issues with listboxes/listviews and their scrolling, however, you mentioned hooking to the listview's changed event, is it because you can't listen to the observable collection's CollectionChanged event? ObservableCollection is way more stable than List controls, and you'll get the same notifications.
You can also bubble these events up if it's not working in the UI and you don't have access, this way you treat your scrolling in the UI without having access to the actual collection, just keep a reference to the Selected Item in your custom EventArgs class

Resources