WPF/C# Forwarding events and DataProvider values within a UserControl - wpf

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?

Related

Share State Between MainWindow and UserControl

I have a WPF application and want to share data between my MainWindow and one or more UserConrols, but for simplicity lets assume I have only one MainWindow.xaml and one UserControl.xaml. From what I've gathered so far, this can be done with Bindings and Properties. So I tried this with no success.
The Object I want to share between the controls looks like this:
SharedObject {
prop string Name;
prop List<Product> Products;
}
Product {
prop string ItemName;
prop double Price;
prop bool Available;
}
So I load the SharedData in the MainWindow and want to be able to edit this in the UserControl, but in TwoWay mode, to get the modified product list updates also in the MainWindow. I also want to access and modify the Name Property of the SharedObject in the UserControl.
How can I achieve this? Is the Property/Binding the way to go? Can this state management also be done in a more elegant way? (in dotnet Core 3.1)
You should set the DataContext of the window to an instance of your view model (SharedObject) and then let the UserControl inherit the DataContext from its parent window (which it does by default).
You can then bind directly to any public property of the view model from the an element in UserControl.

WPF MVVM ViewModel containing ViewModels not updating

I'm very new to WPF and MVVM, and it's been causing me a lot of headaches. Due to issues with navigation, I decided to just have all my content visible at once. I thought I would create a new ViewModel (MainViewModel) to contain my two other ViewModels (StudentViewModel and AddStudentsViewModel).
MainViewModel contains something like this:
private StudentViewModel _studentVM;
private AddStudentsViewModel _addStudentsVM;
public StudentViewModel StudentVM
{
get { return _studentVM; }
set
{
if (_studentVM != value)
{
_studentVM = value;
NotifyPropertyChanged("StudentVM");
}
}
}
(public AddStudentsViewModel AddStudentsVM exists as well, I'm just trying to keep this short)
I have successfully bound StudentVM and AddStudentsVM to my main View, as I can programmatically set values during the initialization phase and when debugging, I can see my button clicks are being redirected to the correct methods. It even seems like I am successfully adding students to objects, however my main View isn't reflecting these changes.
Am I missing something in MainViewModel? Or is it not possible for a ViewModel to see the changes in any other ViewModels inside it?
If your view is bound to some property inside StudentViewModel through nested navigation into the property StudentVM like StudentVM.Property, then to reflect changes on StudentVM.Property unto your view would require that you notify the view that StudentVM (not StudentVM.Property) has changed.
So it depends on how you defined your binding and on which property you're raising the PropertyChanged event.
A viewmodel which contains two other viewmodels. Just think about it for a second. It's not a good idea.
Anyway, you have to implement INotifyPropertyChanged in your containing viewmodels as well. Not only in the containing MainViewModel.
Maybe that is the fault?

How do I get the UIElement when Collection changes?

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.

How to pass an object via event to different viewmodel in MVVM even if no view is rendered yet

I am working on WPF application using MVVM and PRISM and stuck in one issue.
I have two different views(View1 and View2) with their respective view-models.
View1 is main View having a list of domain objects and View2 is used to display the properties of domain object. Now I need to pass the object to View2 every time the selection is changed.
I know that we can do it IEventTrigger but a view model can listen the event only when it is residing in memory.
So here my problem arise. Since the first there is not selected item. The View2 is not rendered. I dont know how to pass the object to the View2 first time via Event.
What can be the possible solution?
Since you said in a comment that you don't want one ViewModel to refer to the other, you can use PRISM's EventAggregator for this instead
Whenever the selection changes, broadcast a SelectionChangedMessage from ViewModel1, and have ViewModel2 subscribe to receive those messages.
If you also need to know the selected item when ViewModel2 is first created, have it broadcast something like a GetCurrentItemMessage, which ViewModel1 can subscribe to receive and would make it re-broadcast the SelectionChangedMessage
Also if you're new to PRISM's EventAggregator, I have a static class on my blog that can be used to simplify how the EventAggregator is used, as I find the default syntax very confusing and hard to understand at first. I use it for most small applications.
If your View1 contains a List which has a SelectedItem property, you could create a SelectedItem-Property in your ViewModel1. The you create a ViewModel2-Property in your ViewModel1.
You bind to it like:
<ListView SelectedItem="{Binding Path=SelectedItem}">
.
.
</ListView>
<my:view2 DataContext="{Binding Path=ViewModel2}"/>
Finally you pass the SelectedItem in the setter of your SelectedItem-Property:
public object SelectedItem
{
get { return _seledtedItem; }
set { _selectedItem = value; ViewModel2.SomeProperty = _selectedItem; OnPropertyChanged("SelectedItem"); }
}

WPF: DependencyProperty of custom control fails when using several instances of the control

I've built a custom control in WPF that inherits from ListBox. In this I have implementet my own property that is a BindingList. To make this property bindable I've implemeneted it as a DependencyProperty:
public BindingList<CheckableListItem> CheckedItems
{
get
{
return (BindingList<CheckableListItem>)GetValue(MultiComboBox.CheckedItemsProperty);
}
set
{
SetValue(MultiComboBox.CheckedItemsProperty, value);
}
}
public static readonly DependencyProperty CheckedItemsProperty;
I register this DependencyProperty in a static constructor inside my custom control:
CheckedItemsProperty = DependencyProperty.Register("CheckedItems",
typeof(BindingList<CheckableListItem>),
typeof(MultiComboBox),
new FrameworkPropertyMetadata(new BindingList<CheckableListItem>()));
(MultiComboBox is the name of my custom control. CheckableListItem is a simple class I've written just for this purpose).
This BindingList is then updated inside the custom control (never outside) as the user interacts with it.
When I use my custom control in XAML I bind to the CheckItems property with the mode "OneWayToSource". I'm using the MVVM pattern and the property in the ViewModel that I'm binding to is also a BindingList. The ViewModel never affects this list, it just reacts at the changes that the custom control make to the list. The property in the ViewModel looks like this:
private BindingList<CheckableListItem> _selectedItems;
public BindingList<CheckableListItem> SelectedItems
{
get
{
return _selectedItems;
}
set
{
if (value != _selectedItems)
{
if (_selectedItems != null)
{
_selectedItems.ListChanged -= SelectedItemsChanged;
}
_selectedItems = value;
if (_selectedItems != null)
{
_selectedItems.ListChanged += SelectedItemsChanged;
}
OnPropertyChanged("SelectedItems");
}
}
}
As you can see I'm listening to changes made to the list (these changes always occur inside my custom control), and in the "SelectedItemsChanged"-method I update my Model accordingly.
Now...this works great when I have one of these controls in my View. However, if I put two (or more) of them in the same View strange things start to happen. This will of course mean that I'll have two lists with selected items in my ViewModel. But if do something in the View that changes one of the lists, both lists are affected! That is, the event handlers for the event ListChanged is triggered for both list if changes are made to any one of them!
Does anyone recognize this problem and/or have a solution to it? What is wrong with my implementation?
My first though is that the DependencyProperty is static. Normally that means shared between all instances. But I guess DependencyProperties work in some other "magical" way so that might not be the problem.
Any tips or hints is appreciated!
I had a similar problem with a collection-type dependency property. My solution was taken from the MSDN article on Collection-Type Dependency Properties. It was adding the following line
SetValue(OperatorsPropertyKey, new List<ListBoxItem>()); //replace key and type
in the constructor of my control because it seems that a collection-type dependency property constructor is being called only once no matter how many instances your control containing this collection has (static eh).
This sounds like you bound both/all the Views to the same ViewModel. That would explain that changes to one cause changes in the other.

Resources