WPF MVVM Bind list on custom control to ViewModel - wpf

Is it possible to bind data in the "wrong" direction? I want a value in a custom control to be bound to my ViewModel. I've tried binding with mode "OneWayToSource" but I can't get it to work.
Scenario (simplified):
I have a custom control (MyCustomControl) that has a dependency property that is a list of strings:
public class MyCustomControl : Control
{
static MyCustomControl()
{
//Make sure the template in Themes/Generic.xaml is used.
DefaultStyleKeyProperty.OverrideMetadata(typeof (MyCustomControl), new FrameworkPropertyMetadata(typeof (MyCustomControl)));
//Create/Register the dependency properties.
CheckedItemsProperty = DependencyProperty.Register("MyStringList", typeof (List<string>), typeof (MyCustomControl), new FrameworkPropertyMetadata(new List<string>()));
}
public List<string> MyStringList
{
get
{
return (List<string>)GetValue(MyCustomControl.MyStringListProperty);
}
set
{
var oldValue = (List<string>)GetValue(MyCustomControl.MyStringListProperty);
var newValue = value;
SetValue(MyCustomControl.MyStringListProperty, newValue);
OnPropertyChanged(new DependencyPropertyChangedEventArgs(MyCustomControl.MyStringListProperty, oldValue, newValue));
}
}
public static readonly DependencyProperty MyStringListProperty;
}
The control also contains code to manipulate this list.
I use this custom control in a UserControl that has a ViewModel. The ViewModel has a property that is also a list of strings:
public List<string> MyStringsInTheViewModel
{
get
{
return _myStringsInTheViewModel;
}
set
{
if (value != _myStringsInTheViewModel)
{
_myStringsInTheViewModel = value;
OnPropertyChanged("MyStringsInTheViewModel");
}
}
}
private List<string> _myStringsInTheViewModel;
Now I want to bind the list in my custom control (MyStringList) to the list in my ViewModel (MyStringsInTheViewModel) so that when the list is changed in the custom control it is also changed in the ViewModel. I've tried this but can't get it to work...
<myns:MyCustomControl MyStringList="{Binding Path=MyStringsInTheViewModel, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}">
How can I make such a binding?

Use ObservableCollection<T> instead of List<T>. It implements INotifyCollectionChanged Interface.

Related

WPF Cannot Bind Dependancy Property From Custom UserControl

I have MultiSelectComboBox UserControl inside my Custom UserControl.
I'd like to bind the SelectedItems Dependency Property (from the MSCB above) which is type of Dictionary to any of My ViewModel Property...
*MSBC means --> MultiSelectComboBox UserControl !!!!!
Code-Behind of the MSCB:
Define DP:
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(Dictionary<string, object>), typeof (MultiSelectComboBox), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(MultiSelectComboBox.OnSelectedItemsChanged)));
Define SelectedItems Property in MSCB:
public Dictionary<string, object> SelectedItems
{
get { return (Dictionary<string, object>)GetValue(SelectedItemsProperty); }
set
{
SetValue(SelectedItemsProperty, value);
}
}
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MultiSelectComboBox ctrl = (MultiSelectComboBox)d;
ctrl.SelectNodes();
ctrl.SetText();
}
Xaml of My Custum UserControl:
Define the MSCB in the Xaml:
<MultiSelectComboBox:MultiSelectComboBox x:Name="WorkDay"
SelectedItems="{Binding SelectedItemsInViewModel}"
ItemsSource="{Binding WorkDays,Converter={StaticResource DataConverter}}"/>
In My ViewModel class:
private Dictionary<string, object> si= new Dictionary<string, object>();
public Dictionary<string, object> SelectedItemsInViewModel
{
get { return si; }
set
{
si = value;
OnPropertyChanged("SelectedItemsInViewModel");
}
}
It looks like the OnEventChanged (of the DProperty inside MSBC) is fired only for the first initialization and then stop firing.
I don't get any changes in my ViewModel property.
I've set the data context to point to my ViewModel class and other bindings inside this CustomControl are working fine (like TextBoxes).
Looks like your code is incomplete.
If the idea is to implement a Multi selection combo box, then you will need to more than just defining a SelectedItems property - you will need populate it when a item is selected/un-selected.
About,
I don't get any changes in my ViewModel property
Are you updating (SetValue) this property from View also? Or is it other way round?
The solution worked for me is to use value converter interface, also you were right about using SetValue(), I've used it but the SelectedItems dictionary didn't set when I change the SelectedItems Dictionary because the DP point to referential object and when the list changing nothing happen unless you change the DP address each time, so I've added this line: SelectedItems = SelectedItems;
private void SetSelectedItems()
{
if (SelectedItems == null)
SelectedItems = new Dictionary<string, object>();
SelectedItems.Clear();
foreach (Node node in _nodeList)
{
if (node.IsSelected && node.Title != "ALL")
{
if (this.ItemsSource.Count > 0)
SelectedItems.Add(node.Title, this.ItemsSource[node.Title]);
}
}
SelectedItems = SelectedItems; //ADDED THIS LINE SOLEVED The DP not changed Problem
}
after that I've used IValueConverter Interface in my ViewModel To handle Dictionary and convert it to what I've needed....
Thanks for helping me out to get the solution , guess I saved few more hours...

Setting WPF datacontext for a specific control

I'm developing a WPF application and I'm struggling a little bit to understand some of the details of DataContext as it applies to binding. My application uses a business object which is defined like this:
public class MyBusinessObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
// enumerations for some properties
public enum MyEnumValues
{
[Description("New York")]
NewYork,
[Description("Chicago")]
Chicago,
[Description("Los Angeles")]
LosAngeles
}
// an example property
private string _myPropertyName;
public string MyPropertyName
{
get { return _myPropertyName; }
set
{
if (_myPropertyName == value)
{
return;
}
_myPropertyName = value;
OnPropertyChanged(new PropertyChangedEventArgs("MyPropertyName"));
}
}
// another example property
private MyEnumValues _myEnumPropertyName;
public MyEnumValues MyEnumPropertyName
{
get { return _myEnumPropertyName; }
set
{
if (_myEnumPropertyName== value)
{
return;
}
_myEnumPropertyName= value;
OnPropertyChanged(new PropertyChangedEventArgs("MyEnumPropertyName"));
}
}
// example list property of type Widget
public List<Widget> MyWidgets { get; set; }
// constructor
public MyBusinessObject()
{
// initialize list of widgets
MyWidgets = new List<Widget>();
// add 10 widgets to the list
for (int i = 1; i <= 10; i++)
{
MyWidgets.Add(new Widget());
}
// set default settings
this.MyPropertyName = string.empty;
}
}
As you can see, I have some properties that are declared in this class one of which is a list of Widgets. The Widget class itself also implements INotifyPropertyChanged and exposes about 30 properties.
My UI has a combobox which is bound to my list of Widgets like this:
MyBusinessObject myBusinessObject = new MyBusinessObject();
public MainWindow()
{
InitializeComponent();
this.DataContext = myBusinessObject;
selectedWidgetComboBox.ItemsSource = myBusinessObject.MyWidgets;
selectedWidgetComboBox.DisplayMemberPath = "WidgetName";
selectedWidgetComboBox.SelectedValuePath = "WidgetName";
}
The majority of the controls on my UI are used to display the properties of a Widget. When my user selects a Widget from the combobox, I want these controls to display the properties for the selected Widget. I'm currently achieving this behavior by updating my window's DataContext in the SelectionChanged event handler of my combobox like this:
private void selectedWidgetComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.DataContext = selectedWidgetComboBox.SelectedItem;
}
This allows me to bind my controls to the appropriate Widget property like this:
<TextBox Text="{Binding WidgetColor}"></TextBox>
However, not all of the controls in my UI are used to display Widget properties. Some of the controls need to display the properties from MyBusinessObject (for example: MyPropertyName defined above). In this scenario, I can't simply say:
<TextBox Text="{Binding MyPropertyName}"></TextBox>
...because the DataContext of the window is pointing to the selected Widget instead of MyBusinessObject. Can anyone tell me how I set the DataContext for a specific control (in XAML) to reference the fact that MyPropertyName is a property of MyBusinessObject? Thank you!
Instead of changing the DataContext of your window, you should add a property to your MyBusinessObject class like this one:
private Widget _selectedWidget;
public Widget SelectedWidget
{
get { return _selectedWidget; }
set
{
if (_selectedWidget == value)
{
return;
}
_selectedWidget = value;
OnPropertyChanged(new PropertyChangedEventArgs("SelectedWidget"));
}
}
Then bind SelectedWidget to the SelectedItem property of your combobox. Anywhere that you need to use the widget's properties you can do this:
<TextBox Text="{Binding Path=SelectedWidget.WidgetColor}"></TextBox>
try
<TextBox Text="{Binding MyBusinessObject.MyPropertyName}"></TextBox>
this works if MyBusinessObject is the datacontext of the textbox and MyPropertyName is a property of MyBusinessObject
Also, Here is a good article to clarify binding
hope this helps
EDIT 1:
use a relative binding like this:
text="{Binding DataContext.MyPropertyName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TypeOfControl}}}"
So the relatve binding allows you to look up the visual tree to another UI element and use its datacontext. I would consider wrapping your window's contents in a grid. and wet your windows datacontext to the businessobject and the grids datacontext to the widget. That way you can always use the parent window's datacontext through the realtive source binding.
so use the following if your window's datacontext is your business object
text="{Binding DataContext.MyPropertyName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"

How to create silverlight 4 usercontrol with dependency property that can accept any type

I am looking for a way to create an UserControl in silverlight 4 and expose a dependency property, which can accept any type. What I mean by that is, for example, if you look at standard silverlight control like AutoCompleteBox, it is capable of handling any type of collections. So you can bind AutoCompleteBox with IEnumerable<Human> or IENumerable<Animal> etc. And when any item is selected AutoCompleteBox returns the selected value either Human instance or Animal instance via SelectedItem dependency property.
I want to achieve similar flexibility with my usercontrol. I wouild like to expose 2 dependency properties SuggestedItems and SelectedItem. Which ever collection is set to SuggestedItems via consumers of this usercontrol thru Binding, lets take as an example IEnumerable<Car>, the I want SelectedItem property to send instance of Car type back to consumer thru Binding. If I used IEnumerable<Boat>, then I need Boat to be returned with SelectedItem.
I was trying to achieve it by using below example using MVVM, but its not working. I am looking for some clues as to how it should be designed, Am I even on a correct path or I have to completely alter my design?
I created an UserControl called VehicleSelectorUserControl which has its own dedicated ViewModel called VehicleSelectorViewModel with two proerties SuggestedItems, SelectedItem.
And usercontrol has corresponding Dependency properties in its codebehind to expose them to consumers of usercontrol. UserControl XAML has a ListBox which is bound to SuggestedItems property of VehicleSelectorViewModel. When user makes a selection, VehicleSelectorViewModel SelectedItem is set, which them invokes a delegate called ItemSelected to notify VehicleSelectorUserControl codebehind, which then sets the SelectedItem Dependency property to make it available to consumer.
Below is code from the VehicleSelectorUserControl.xaml.cs code behind.
private VehicleSelectorViewModel _TheViewModel;
public UserNameControl()
{
InitializeComponent();
_TheViewModel = Resources["TheViewModel"] as VehicleSelectorViewModel;
_TheViewModel.ItemSelected = OnItemSelected;
}
public IEnumerable<object> SuggestedItems
{
get { return (IEnumerable<object>)GetValue(SuggestedItemsProperty); }
set { SetValue(SuggestedItemsProperty, value); }
}
public static readonly DependencyProperty SuggestedItemsProperty =
DependencyProperty.Register("SuggestedItems", typeof(IEnumerable<object>), typeof(VehicleSelectorControl), new PropertyMetadata(OnSuggestedItemsSet));
private static void OnSuggestedItemsSet(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
_TheViewModel.SuggestedItems = e.NewValue;
}
public object SelectedItem
{
get { return (String) GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(VehicleSelectorControl), null);
private void OnItemSelected()
{
SelectedItem = _TheViewModel.SelectedItem;
}
Its ViewModel VehicleSelectorViewModel code
public Action ItemSelected { get; set; }
private dynamic _SelectedItem;
public dynamic SelectedItem
{
get { return _SelectedItem; }
set
{
if (value != _SelectedItem)
{
_SelectedItem = value;
NotifyPropertyChanged("SelectedItem");
if(ItemSelected != null) ItemSelected.Invoke();
}
}
}
private dynamic _SuggestedItems;
public dynamic SuggestedItems
{
get { return _SuggestedItems; }
set
{
if (value != _SuggestedItems)
{
_SuggestedItems = value;
NotifyPropertyChanged("SuggestedItems");
}
}
}
The XAML of consumer will look like (Consumer has its own ViewModel, which responsible for supplying SuggestedCars [IEnumerable<Car>], SuggestedBoats [IEnumerable<Boat>].
<my:VehicleSelectorControl x:Name="MyCarSelectorControl"
SuggestedItems="{Binding SuggestedCars, Mode=TwoWay}"
SelectedItem="{Binding UserSelectedCar, Mode=TwoWay}" />
<my:VehicleSelectorControl x:Name="MyBoatSelectorControl"
SuggestedItems="{Binding SuggestedBoats, Mode=TwoWay}"
SelectedItem="{Binding UserSelectedBoat, Mode=TwoWay}" />

User Control with custom ItemsSource dependency property

I've got a UserControl with an ItemsSource property. As the base UserControl class does not implement ItemsSource, I had to create my own dependency property like this:
#region ItemsSource Dependency Property
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(MonthViewControl),
new PropertyMetadata(OnItemsSourceChanged));
static void OnItemsSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
(obj as MonthViewControl).OnItemsSourceChanged(e);
}
private void OnItemsSourceChanged(DependencyPropertyChangedEventArgs e)
{
RefreshLayout();
}
public IEnumerable ItemsSource
{
get
{
return (base.GetValue(ItemsSourceProperty) as IEnumerable);
}
set
{
base.SetValue(ItemsSourceProperty, value);
}
}
#endregion
Now in my ViewModel I have an Events property which is an ICollectionView of EventItem items like so:
private ObservableCollection<Controls.EventCalendar.EventItem> eventItems;
private CollectionViewSource events;
public System.ComponentModel.ICollectionView Events
{
get
{
if (events == null)
{
events = new CollectionViewSource();
events.Source = eventItems;
}
return events.View;
}
}
The issue I'm facing is that in my View, when I bind to the Events property, and I add an Item to eventItems, the UserControl won't fire the ItemsSourceChanged event and hence not update the UI.
For the sake of testing I added a simple listbox to the view which also binds to the Events property. That works like a charm. Updates to eventItems observableCollection are reflected in the ListBox.
I'm figuring it has something to do with my ItemsSource dependency property. Maybe I would need to use a Custom Control which inherits form ItemsControl instead of a UserControl?
To help you understand my problem: I'm trying to create a calendar like control which shows events/agenda entries (similar to Google Calendar). It works like a charm. The UI is updated when the control is resized. The only thing that's left is the automagical update once the ItemsSource changes.
Hope someone can help.
EDIT: The moment I posted I realized that the event can't be fired as the ItemsSource property does not change. It is the underlying collection that changes. However, I'm not how to handle that. What do I need to implement to make this work. Just a hint would be enough. I don't need every implementation details.
Opening the PresentationFramework.dll within Reflector and looking at System.Windows.Controls.ItemsControl showed the following:
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable),
typeof(ItemsControl), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(ItemsControl.OnItemsSourceChanged)));
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ItemsControl control = (ItemsControl) d;
IEnumerable oldValue = (IEnumerable) e.OldValue;
IEnumerable newValue = (IEnumerable) e.NewValue;
ItemValueStorageField.ClearValue(d);
if ((e.NewValue == null) && !BindingOperations.IsDataBound(d, ItemsSourceProperty))
{
control.Items.ClearItemsSource();
}
else
{
control.Items.SetItemsSource(newValue);
}
control.OnItemsSourceChanged(oldValue, newValue);
}
Not knowing what RefreshLayout does my hunch is that it has something to do with the way the ObservableCollection<T> is being wrapped as the above code is oblivious to what the concrete collection type is and it would therefore be handled by the type being wrapped; in this case an ObservableCollection<T> Try modifying your property as seen below to return the default view and adjust your ItemsSource property to be more akin to the above code from the framework and work backwards from there.
private ObservableCollection<Controls.EventCalendar.EventItem> eventItems;
private ICollectionview eventsView;
public System.ComponentModel.ICollectionView Events
{
get
{
if (eventsView == null)
eventsView = CollectionViewSource.GetDefaultView(eventItems);
return eventsView;
}
}

WPF DataContext does not refresh the DataGrid using MVVM model

Project Overview
I have a view which binds to a viewmodel containing 2 ObserverableCollection. The viewmodel constructor populates the first ObserverableCollection and the view datacontext is collected to bind to it through a public property called Sites.
Later the 2ed ObserverableCollection is populated in the LoadOrders method and the public property LoadFraudResults is updated for binding it with datacontext.
I am using WCF to pull the data from the database and its getting pulled very nicely.
VIEWMODEL SOURCE
class ManageFraudOrderViewModel:ViewModelBase
{
#region Fields
private readonly ICollectionView collectionViewSites;
private readonly ICollectionView collectionView;
private ObservableCollection<GeneralAdminService.Website> _sites;
private ObservableCollection<FraudService.OrderQueue> _LoadFraudResults;
#endregion
#region Properties
public ObservableCollection<GeneralAdminService.Website> Sites
{
get { return this._sites; }
}
public ObservableCollection<FraudService.OrderQueue> LoadFraudResults
{
get { return this._LoadFraudResults;}
}
#endregion
public ManageFraudOrderViewModel()
{
//Get values from wfc service model
GeneralAdminService.GeneralAdminServiceClient generalAdminServiceClient = new GeneralAdminServiceClient();
GeneralAdminService.Website[] websites = generalAdminServiceClient.GetWebsites();
//Get values from wfc service model
if (websites.Length > 0)
{
_sites = new ObservableCollection<Wqn.Administration.UI.GeneralAdminService.Website>();
foreach (GeneralAdminService.Website website in websites)
{
_sites.Add((Wqn.Administration.UI.GeneralAdminService.Website)website);
}
this.collectionViewSites= CollectionViewSource.GetDefaultView(this._sites);
}
generalAdminServiceClient.Close();
}
public void LoadOrders(Wqn.Administration.UI.FraudService.Website website)
{
//Get values from wfc service model
FraudServiceClient fraudServiceClient = new FraudServiceClient();
FraudService.OrderQueue[] OrderQueue = fraudServiceClient.GetFraudOrders(website);
//Get values from wfc service model
if (OrderQueue.Length > 0)
{
_LoadFraudResults = new ObservableCollection<Wqn.Administration.UI.FraudService.OrderQueue>();
foreach (FraudService.OrderQueue orderQueue in OrderQueue)
{
_LoadFraudResults.Add(orderQueue);
}
}
this.collectionViewSites= CollectionViewSource.GetDefaultView(this._LoadFraudResults);
fraudServiceClient.Close();
}
}
VIEW SOURCE
public partial class OrderQueueControl : UserControl
{
private ManageFraudOrderViewModel manageFraudOrderViewModel ;
private OrderQueue orderQueue;
private ButtonAction ButtonAction;
private DispatcherTimer dispatcherTimer;
public OrderQueueControl()
{
LoadOrderQueueForm();
}
#region LoadOrderQueueForm
private void LoadOrderQueueForm()
{
//for binding the first observablecollection
manageFraudOrderViewModel = new ManageFraudOrderViewModel();
this.DataContext = manageFraudOrderViewModel;
}
#endregion
private void cmbWebsite_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
BindItemsSource();
}
#region BindItemsSource
private void BindItemsSource()
{
using (OverrideCursor cursor = new OverrideCursor(Cursors.Wait))
{
if (!string.IsNullOrEmpty(Convert.ToString(cmbWebsite.SelectedItem)))
{
Wqn.Administration.UI.FraudService.Website website = (Wqn.Administration.UI.FraudService.Website)Enum.Parse(typeof(Wqn.Administration.UI.FraudService.Website),cmbWebsite.SelectedItem.ToString());
//for binding the second observablecollection*******
manageFraudOrderViewModel.LoadOrders(website);
this.DataContext = manageFraudOrderViewModel;
//for binding the second observablecollection*******
}
}
}
#endregion
}
XAML
ComboBox x:Name="cmbWebsite" ItemsSource="{Binding Sites}" Margin="5"
Width="100" Height="25" SelectionChanged="cmbWebsite_SelectionChanged"
DataGrid ItemsSource ={Binding Path = LoadFraudResults}
PROBLEM AREA:
When I call the LoadOrderQueueForm to bind the first observablecollection and later BindItemsSource to bind 2ed observable collection, everything works fine and no problem for the first time binding.
But, when I call BindItemsSource again to repopulate the obseravablecollection based on changed selected combo value via cmbWebsite_SelectionChanged, the observalblecollection gets populated with new value and LoadFraudResults property in viewmodule is populated with new values; but when i call the datacontext to rebind the datagrid,the datagrid does not reflect the changed values.
In other words the datagrid doesnot get changed when the datacontext is called the 2ed time in BindItemsSource method of the view.
manageFraudOrderViewModel.LoadOrders(website);
this.DataContext = manageFraudOrderViewModel;
manageFraudOrderViewModel values are correct but the datagrid is not relected with changed values.
Please help as I am stuck with this thing for past 2 days and the deadline is approaching near.
Thanks in advance
try to use datagrid.Items.Refresh() !
Yes, ilu2009 is correct.
Binding using the MVVM modal to a DataGrid and changing the objects in DataGrid.ItemsSource requires DataGrid.ItemsSource.Refresh() for it to reflect on the UI.

Resources