I have a wpf Treeview which has a dynamic itemssource. The User can add and remove items at runtime.
I'm missing an event which gives me the currently added UIElement that was added to the treeviews itemsSource. So I guess I need to switch to OnCollectionChanged.
This is what I have:
// MyItemViewModel is a viewmodel for a TreeViewItem
// MyCollection is bound to hte Treeview's ItemsSource
public class MyCollection : ObservableCollection<MyItemViewModel>
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
// i like to have the UIelement which was added to the collection
// (e.NewItems contains the FrameworkElement's DataContext)
break;
}
}
}
Im following MVVM, as good as I can, and don't want to hold any view elements in the viewmodel.
I like to have an event that is fired when an item is added, which provides the new added UIElement in its sender or EventArgs.
I already tried ItemContainerGenerator class, but it's not useful inside a viewmodel since it requires already a UIElement Control.
You seem to be looking at this problem from the wrong direction... in MVVM, you can pretty much forget about the UI for the most part. So, instead of thinking how to get hold of the item that the user added into the collection control in the UI, think about accessing the data object that you added to the data collection in the view model that is data bound to the UI collection control in response to an ICommand that was initiated by the user.
So to me, it sounds like you need to implement an ICommand that is connected to a Button in the UI, where you add the new item into the data bound collection rather than any event. In this way, you'll always know the state of all of your data items.
Related
I have a TreeView control, and after a new node is added, I need to execute some custom code (ex expand the node, make it visible, and fire begind edit method). Since I want this to be available in every instance of TreeView control, I thought of subclassing the TreeView control.
Now, in order for treeview to know when the new node is added, it would either have an event that is fired when item is added (which it doesn't), or to have a reference to command that was executed to add a new item.
So two questions:
1) Is there a way to add an event in TreeView that would be fired whenever a treenode is added (I am always adding nodes through source collection from ViewModel) - I could not find any way to do this
2) I could add an AddCommand property to TreeList, that would be bound to ViewModel's AddCommand, and then have some button, or ContextMenu item that would bind to TreeList.AddCommand, instead of view model. This way TreeView would hold reference to AddCommand, but the drawback would be that actual usage would be kind of wierd. Question: How can I know when an TreeView's AddCommand (or any command, to that matter) is executed, so I can fire some custom code after it? It seems that CommandManager.AddExecutedHandler is a solution, but I am unable to execute it.
Is this any help?
public class CustomTreeControl : TreeView
{
...
// WPF only
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
base.OnItemsSourceChanged(oldValue, newValue);
Debug.WriteLine("OnItemsSourceChanged");
}
// WPF + Silverlight
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
Debug.WriteLine("OnItemsChanged: {0}", e.Action);
}
}
I've got a datababound TabControl and would like to bind the index of each TabItem to a corresponding property in my view model. The ItemsSource is an ObservableCollection, and I'm using Bea Stollnitz's Drag/Drop functionality to provide tab control re-ordering.
My gut feeling is that it should be able to be handled in the data template for the tab item header, but I haven't been able to get it working.
Your TabControl.ItemsSource should be bound to your collection, so to re-arrange the order of tab items, simply re-arrange the collection.
I've worked with Bea's drag/drop code before to create a TabControl that allowed users to drag/drop the tab items, and I think most of what was needed is in the code she provides. On drop, it removes the dragged object from it's parent collection, and inserts it to its new location in the drop target collection, which in your case is the same collection.
Edit
Based on your comment below about updating your ViewModel with the Tab Index, try using the CollectionChanged event.
void MyCollection_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
foreach (var item in MyCollection)
item.TabIndex = MyCollection.IndexOf(item);
}
I have a UserControl which contains 4 ToggleButtons and I'd like to trigger a custom event that an interested object can listen for which provides a status, based on the ToggleButton Checked values and also value(s) from the DataContext object.
Getting the ToggleButton checked values and deriving a status is simple enough, however I can't work out how I access the DataContext object within the C# codebehind.
For example, if an interested object receives the RoutedEvent from the UserControl, I would like it to be able to access values from the UserControl's DataContext object.
Will I need to expose specific properties from the DataContext object or can I somehow expose the DataContext object from the UserControl's API?
Update.
To explain the problem a little more, I have a list of items which creates a set of UserControl instances in a container, I attach event listeners to each item as it's added to the container and send an event from one of the UserControls when it's child controls are clicked / checked etc.
Getting a reference to the UserControl that dispatched the event is straightforward enough, but I can't access the DataContext object, do I need to assign a public property to expose the DataContext object ...
e.g.
private ControlViewModel myControlViewModel;
public ControlViewModel MyControlViewModel {
get { return myControlViewModel; }
set
{
this.DataContext = value;
myControlViewModel = value;
}
}
or is there a better way?
Any tips would be appreciated, Thank you.
Well, it looks like I should've tried the simplest solution first...
...of course I can access the DataContext object like this:
(userControl.DataContext as ControlViewModel).requiredProperty;
Update
So I ended up passing the DataContext view model reference via a event/delegate pair like this...
public delegate void StatusChangedHandler(string status, UserControlViewModel model);
public event StatusChangedHandler StatusChanged;
And then just invoked the event like this...
StatusChanged.Invoke("message", DataContext as UserControlViewModel)
// or DataContext as IUserControlModelInterface
Which allowed me to adequately aggregate events from the UserContol's child controls, and access the DataContext model from an event handler.
I still wonder if there is a more best practice way to do this?
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 ?
I've written a Custom WPF Control with search extension, let's name it MyControl.
The Control is a descendent of an ItemsControl class.
So I feed the the data source to it like this:
The control itself uses
protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue)
{
if (newValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += this.FilterPredicate;
}
if (oldValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(oldValue);
view.Filter -= this.FilterPredicate;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
to filter the view of the source collection (thus displaying it in an inner ListBox).
Now suppose we have 10 of these MyControls defined in XAML with the same DynamicSource.
The problem is that if one of them applies the Filter on the source collection, it will affect all other instances too.
How would you change the Control to avoid this behaviour ?
In situations like this you would generally want to create a separate ICollectionView instance for each differently filtered usage of the collection. It's not a good idea to use a specific implementation of ICollectionView since it's possible for the CollectionView type needed to change if the ItemsSource is bound to a different type of collection. Using
ICollectionView filteredView = new CollectionViewSource { Source=newValue }.View;
will give you an ICollectionView that's the correct type automatically.
Unfortunately, what you may find in this case is that it is very difficult to apply a different collection to the ItemsPresenter of your custom control since all of that magic is done for you by the base ItemsControl class and relies on the ItemsSource/Items properties which it manages. This happens when using something similar to ItemsControl's default template.
If you are in fact using a separate ListBox control (and TemplateBinding all the ItemsSource properties if you need them) inside your ControlTemplate then you should be able to simply add a new ICollectionView DP (I'd recommend read-only) on your control to hold your filtered version of the collection and bind the template ListBox's ItemsSource to that new property.
The problem there is that CollectionViewSource.GetDefaultView(object) will always return the same ICollectionView instance for a given source, and this is what any ItemsControl extension will use when displaying that source.
You can get around this by creating a new instance of ICollectionView to be used by each control that you want to be able to independently filter the collection, and then explicitly binding the ItemsSource property of each control to that specific view. The type of ICollectionView needed would depend on your scenario, but ListCollectionView is generally appropriate.