What happens internally when you bind to ItemSource? - c#

I'm curious how this works, because I have a MainViewModel, which has Property say called SubViewModel which has a Property of ObservableCollection (we'll call it Property1.)
I've implemented INotifyChangedProperty on everything.
My Main Window
<Window ..
DataContext="{Binding MainViewModel}" />
...
<StackPanel DataContext="{Binding SubViewModel}">
<local:SomeControl DataContext="{Binding}" />
</StackPanel>
</Window>
And my UserControl
<UserControl Name="SomeControl">
<DataGrid Name="MyDataGrid" ItemSource="{Binding Property1, Mode=TwoWay}" CurrentCellChanged="TestMethod" />
...
</UserControl>
In my test method, just as a test to figure out why the changes are not propegating up to the main view model I do something like this
private void TestMethod()
{
var vm = this.DataContext as SubViewModel;
var itemSourceObservableCollection = MyDataGrid.ItemsSource as ObservableCollection<MyType>;
//I thought vm.Property1 would be equal to itemSourceObservableCollection
//but they are not, itemSourceObservableCollection shows the changes I've made
//vm.Property1 has not reflected any changes made, even though I though they were the same item
}
So I figured out that ItemSource must create a copy of the item you bind it to? I'm stuck here, how do manually notify the viewModel that this property has changed and it needs to update? I thought that was INotifyPropertyChanged's job?
I think part of my problem is I lack the understanding of how this kinda works internally. If anyone can point to a good blog post, or documentation to help me understand why my code isn't working the way I expected, that would be great.

1) No copy is made.
2) ObservableCollection will propogate changes made to the collection, not the items within the collection. So you'll see additions, deletions etc. but NOT property changes to items within the collection.
3) If you want to see changes made to individual items in the ObservableCollection, you need to implement INotifyPropertyChanged on those items.

There's actually TWO different issues here. What happens internally when you bind to a collection? AND why changes on the user surface are not propagated back to your View Model. Based upon what you wrote, the two issues are not connected, but let's take them one at a time...
For the first issue... When you bind a collection, the WPF binding engine creates a "CollectionView" class that mediates between your object store and the logical tree. You can, if needed, get a copy of the the "CollectionView" using a static method on CollectionViewSource...
var cvs = CollectionViewSource.GetDefaultView(MyCollectionOfThings);
There are several interesting properties in the result, and some of them contain write accessors which allow you to directory modify the CollectionView.
For the second issue... The business classes in your SubViewModel need to inherit from INotifyPropertyChanged such that changes are 'announced' via the WPF binding engine. Your VM should be a publisher, but can also be a subscriber. A property that participates in the INotifyPropertyChanged plumbing gets declared like this...
private string _name;
[Description("Name of the driver")]
public string Name
{
[DebuggerStepThrough]
get { return _name; }
[DebuggerStepThrough]
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
This code publishes changes, but can also subscribe to changes made on the user surface by setting the appropriate attributes in your Xaml.
Background reading: What is a CollectionView?
Also, Similar question

Related

C#/WINUI3: Data binding becomes invalid after using the OrderBy() to an ObservableCollection

Part of the .xaml:
...
<ListView x:Name="DeviceList" ItemsSource="{x:Bind ViewModel.FliteredDevicesCollection,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local1:BleDevice">
...
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
...
I created a custom class named BleDivice, and I bind the ItemsSource of the ListView to ObservableCollection<BleDevice> FliteredDevicesCollection to show the devices in the collection. The device list can be updated with the collection dynamically, which worked well.
Then I tried to reorder the devices in the collection I used FliteredDevicesCollection = new ObservableCollection<BleDevice>(FliteredDevicesCollection.OrderBy(BleDevice => BleDevice.SignalStrength)); , however, the bind became invalid.
I printed the contents in the collection through Debug and found that it was indeed reordered, but the items in the listview no longer changed with the data in the collection. It seemed that the data binding had failed.
Is it caused by this method OrderBy()? I tried the indirect way, but it still didn't solve the problem.
So what is the reason and how to solve this problem, instead of using methods such as DeviceList.ItemsSource = ViewModel.FliteredDevicesCollection; in the .xaml.cs file after each reordering to rebind the data.
New
private ObservableCollection<BleDevice> flierDevicesCollection { get; set; } = new();
public ObservableCollection<BleDevice> FliteredDevicesCollection
{
get => flierDevicesCollection;
set
{
flierDevicesCollection = value;
OnPropertyChanged("FliteredDevicesCollection");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
I have already added the code above, but it doesn't work. It is strange that I can add objects to the collection or remove objects from the collectionFliteredDevicesCollection.Add(bleDevice);, the ListView is updated normally, but I can't reorder the objects in the collection. These should all send notifications. Actually, the reorder progress has been done, but the binding between them failed, and the result couldn't be shown in the ListView. So what's the reason?
You don't show how you define your FliteredDevicesCollection property, but taking a wild guess I'd say it doesn't notify on property changes, which is correct. What you're doing is a lazy anti-pattern.
What you should be doing is to bind to a CollectionViewSource, and set up filtering and/or ordering using the respective properties of that object. That is literally why that object exists in all XAML frameworks.

Custom XAML Binding SOURCE Class

I am trying to find a better way to declare the ViewModel a UWP XAML Page uses.
At this moment,
I declare a ViewModel class ViewModelClass that contains my data properties.
Then I declare an instance of that class as StaticResource of the Page. I like calling those VIEW for consistency across all my Page designs.
Finally, I declare the Page's DataContext as a Binding to the StaticResource VIEW.
This yields a page that understands what data structure is in use and allows AutoComplete when working Bindings. Nice, though lots of lines of same-old-same-old code.
Only, it is not really suitable to ViewModels as the declared resource is a static resource. It is instantiated when the page is instantiated. Most pages will receive a ViewModel parameter upon NavigatedTo, which cannot be used to replace the static resource, because it is, well. static.
So I end up changing the Page's DataContext upon navigation from the initial reference to VIEW to the ViewModel instance I actually want to use.
Big caveat is to declare the back-reference to the page's DataContext when deep in the bowels of a Master-Detail situation is rather horrible. Imagine a collection whose display is in part depending on a Master's property.
How do you tie back robustly to the DataContext of the page from anywhere inside the page?
I have tried giving the page a Name (PAGE for simplicity) and then using ElementName=PAGE, Path=DataContext.someProperty. Ugly, plus you lose all information of the class represented by DataContext.
Another approach is to create a Wrapper around the actual ViewModel called StaticViewModel that has only one property: public ViewModel viewModel. Now I can declare the wrapper as a StaticResource, and tell the page's top-level FrameworkElement to use VIEW.viewModel as its DataContext. Works, and reliably, but sooooo ackward and cumbersome.
I would LOVE to implement a SOURCE class for bindings called PageDataContext that would do nothing else but to loop into the page and get the DataContext from there.
Imagine: {Binding someProperty, Source={PageDataContext}
How would I go about declaring said Source class for a UWP app???
I would LOVE to implement a SOURCE class for bindings called PageDataContext that would do nothing else but to loop into the page and get the DataContext from there. Imagine: {Binding someProperty, Source={PageDataContext}
For your requriment, you could implement your viewmodel in the page Resources and give it x:key. When you bind the property of viewmodel you could access this viewmodel with x:key Source={StaticResource ViewModel} for more please refer the following code.
ViewModel
public class ViewModel
{
public ViewModel()
{
Visibility = false;
}
public bool Visibility { get; set; }
}
Xaml
<Page.Resources>
<local:ViewModel x:Key="ViewModel" />
</Page.Resources>
<TextBlock
Width="100"
Height="44"
Text="{x:Bind Name}"
Visibility="{Binding Visibility, Source={StaticResource ViewModel}}" />
Well, no dice so far.
Working with all those options I found that the best one is a Wrapper class that can be assigned to a static resource which in turn is the basis for the page's DataContext. The wrapped instance of the actual ViewModel is then assigned during OnNavigatedTo() and used as the DataContext of the basic FrameworkElement of the page.
Brr, so much verbose code.
STILL WONDERING how to implement a different version of a source directive. Is there no way to declare one's own Source for a Data Binding? Is this stuff really hard-coded into the framework???

To use (DataContext) or not to use

I've got a dilemma regarding the DataContext. Let's inspect the following piece of XAML:
<Window xmlns:my="clr-namespace:MyNamespace.Controls"
... >
...
<my:MyControl Name="{Binding Prop1}" Value="{Binding Prop2}" />
</Window>
Obviously, the Window's code-behind contains something like:
DataContext = someViewModel;
Author's intentions are clear - he wants to bind MyControl's Name and Value to Window's DataContext's Prop1 and Prop2. And this will of course work. Unless. (dramatic pause)
Unless MyControl is a composite UserControl, which also wants to take advantage of short notation of bindings and sets its DataContext to its own viewmodel. Because then it will become clear, that the bindings in Window's XAML actually bind to MyControl's DataContext (previously inherited from Window's one) and now they will stop working (or worse, will keep working if MyControl's viewmodel actually contains properties named Prop1 and Prop21).
In this particular case solution is to bind in Window's code explicitly:
<Window x:Name="rootControl"
xmlns:my="clr-namespace:MyNamespace.Controls"
... >
...
<my:MyControl Name="{Binding ElementName=rootControl, Path=DataContext.Prop1}"
Value="{Binding ElementName=rootControl, Path=DataContext.Prop2}" />
</Window>
TL;DR If we're using short notation of bindings (when binding to DataContext) we may encounter quite tough to nail bugs resulting from bindings suddenly pointing to wrong DataContext.
My question is: how to use short binding notation without risk, that I'll bind to wrong DataContext? Of course I may use the short notation when I'm sure, that I'll be using inherited DataContext and long notation when I'm sure, that control will modify its DataContext. But that "I'm sure" will work only until first mistake, which will consume another hour of debugging.
Maybe I'm not following some MVVM rule? E.g. for example DataContext should be set only once on the top level and all composited controls should bind to something else?
1 I've gone through that, actually. The Window's DataContext contained a property named (say) Prop and the control replaced its DataContext with a class, which also contained a property Prop and everything worked fine. Problem appeared when I tried to use (unconsciously) the same pattern with non-matching property names.
By request:
Fragment of MyControl's code:
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
// Using a DependencyProperty as the backing store for Name. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(MyControl), new PropertyMetadata(null));
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(int), typeof(MyControl), new PropertyMetadata(0));
Window's viewmodel:
public class WindowViewmodel : INotifyPropertyChanged
{
// (...)
public string Prop1
{
get
{
return prop1;
}
set
{
prop1 = value;
OnPropertyChanged("Prop1");
}
}
public int Prop2
{
get
{
return prop2;
}
set
{
prop2 = value;
OnPropertyChanged("Prop2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Now assume, that on changing of Name and Value dependency properties, MyControl generates some viewmodel and executes the code:
model = new MyControlViewModel(Name, Value);
this.DataContext = model;
And internal MyControl controls bind to this DataContext.
From now on, the original Name and Value bindings will no longer work.
Unless MyControl is a composite UserControl, which also wants to take advantage of short notation of bindings and sets its DataContext to its own viewmodel.
And that's where I stopped reading. This is, imho, a MVVM anti-pattern.
The reason for this is twofold. First, you screw with anybody who is using the control. "Hey," you say, "you can't bind your stinky VM to my beautiful UI. You have to use MY custom VM!" But what if your VM is hard to use, lacks logic or features needed by the overall application? What happens when, to use your UI, we have to translate our VM/models back and forth with your VM? Pain in the butt.
Second is that your custom control is UI. Its logic is UI logic, and so it is unnecessary to use a view model. It is better to expose DependencyProperties on your control and update your UI as necessary. That way anybody can bind to your UI and use it with any model or view model.
You can solve your problems by simply not using what you call a 'composite control. While I understand that you want to encapsulate some functionality in the associated view model, you don't need to set the view model to the UserControl.DataContext internally.
What I mean by this is that you can have a view model for any or all of your UserControls, but they're data classes, not UI classes, so keep them out of the view code. If you use this method of adding DataTemplates into Resources, then you won't need to set any DataContext properties at all:
<DataTemplate DataType="{x:Type ViewModels:YourUserControlViewModel}">
<Views:YourUserControl />
</DataTemplate>
The final difference is that you should add your view model for your UserControls as properties in a parent view model. This way, you still have no duplicated code (except maybe just a property declaration) and more importantly, you have no Binding problems from mixing DataContext values.
UPDATE >>>
When using this DataTemplate method of hooking up views and view models, you can display your view by Binding your view model property to the Content property of a ContentControl like this:
<ContentControl Content="{Binding YourViewModelProperty}" />
At run time, this ContentControl will be rendered as whatever view or UserControl that you defined in the DataTemplate of the relevant type for that property. Note that you shouldn't set the x:Key of the DataTemplate, otherwise you'd also need to set the ContentControl.ContentTemplate property and that can limit the possibilities afforded by this method.
For example, without setting the x:Key property on your DataTemplates, you could have a property of a base type and by setting it to different sub class, you can have different views for each from the one ContentControl. That is the basis of all of my views... I have one property of a base class view model data bound like this example and to change views, I just change the property to a new view model that is derived from the base class.
UPDATE 2 >>>
Here's the thing... you shouldn't have any 'proxy' object in your UserControls doing anything... it should all be done through properties. So just declare a DependencyProperty of the type of that object and supply it from the view model through data Binding. Doing it this way means that it will be easy to test the functionality of that class, whereas testing code behind views is not.
And finally, yes, it's perfectly fine doing this in MVVM:
<Controls:SomeUserControl DataContext="{Binding SomeViewModelProperty}" />
The overriding goal of MVVM is just to provide separation between the UI code and the view model code, so that we can easily test what's in the view models. That is why we try to remove as much functionality code from the views as possible.
within a usercontrol you should never set the datacontext to "this" or a new viewmodel. a developer/user of your MyUsercontrol expect that the datacontext inherit from top to bottom (from mainwindow to your myusercontrol).
your usercontrol xaml should use element binding
MyUserControl.xaml
<UserControl x:Name="uc">
<TextBlock Text="{Binding ElementName=uc, Path=Name}"/>
<TextBlock Text="{Binding ElementName=uc, Path=Value}"/>
this means your following code will work now in every situation
<Window xmlns:my="clr-namespace:MyNamespace.Controls">
<my:MyControl Name="{Binding Prop1}" Value="{Binding Prop2}" />
</Window>
the property Prop1 from Datacontext mainwindow is bound to the DP Name from your MyUsercontrol and the Textblock.Text within your MyUsercontrol is bound to the DP Name.
I've never met such a problem. It seems to be a little bit theoretical to me but maybe because of my approach to working with DataContext in WPF.
I minimize the explicit use DataContext property. I set it manually only for windows.
I have one dedicated method which is responsible for displaying new windows and it is the only one place where the DataContext property is set explicitly.
DataContext property for Windows is set to root ViewModel which contains child ViewModels, which contain...
I allow WPF to select which View should be used to display given a ViewModel by using DataTemplate
In my application I have a single ResourceDictionary which contains mappings between all ViewModels and Views.

MVVM setting default values

I'm sure this is a pretty common scenario, and I would like to know how MVVM developers tackle this.
I have a ViewModel that is instantiated on demand, and persists until it is explicitly removed by the user. It's corresponding View is loaded onto the UI on demand. The View is able to unload and it's ViewModel may still exists in the application.
In my scenario, I have a ListBox of preset colors in the View(by setting it's ItemsSource to a Xaml-defined ObservableCollection of SolidColorBrush).
I bound the ListBox's SelectedItem property to a property in the ViewModel so that when the View is to be loaded again, the SelectedItem correctly shows the last selected item in the ListBox and also when the user selects a different color, the VM will handle the change.
My question is, how would u set the default value, say the third item in the ObservableCollection of SolidColorBrush to the ViewModel when the View is first loaded?
Usually I set the defaults in the Constructor, unless the defaults may take some time to load, in which case I'll set call a method to set it in the getter for the bound property.
The reason for this is to simplify maintenance. If I am looking for where I set the default value so I can view or change it, the first place I check is the constructor. It's easier to find than scrolling through the properties, and is known to contain initialization logic.
MyViewModel()
{
// Set defaults
SelectedColor = Brushes.Red;
}
For properties that may take longer to load, I use a method that is called in the getter for the same reason. Typically all my properties and their getters/setters are hidden in a region, and I find it much easier to find a method called LoadColors() in my class than finding the Colors properties in the huge list of properties I have. Also, it's reusable, so if I need to do something like reset the value, its easy to do so without repeating my code.
ObservableCollection<SolidColorBrush> Colors
{
get
{
if (_colors == null)
LoadColors();
return _colors;
}
set { ... }
}
void LoadColors()
{
// Initialization logic here
}
You can also set the default in your XAML by using the FallbackValue of the binding, however this usually only makes sense when there is a possibility of the binding's DataContext not existing when the binding is evaluated.
<!-- You may have to look up the exact syntax for Brushes.Red -->
<ListBox SelectedItem="{Binding SelectedColor, FallbackValue=Red}" />
And last but not least, you can always resort to code-behind the view to execute view-specific logic like your example. For example,
void ComboBox_Loaded(object obj, EventArgs e)
{
if (MyComboBox.SelectedIndex == -1)
MyComboBox.SelectedIndex = 2;
}
I believe you error is in your implementation. The reason to have MVVM is to have a "separation of concerns". That makes your view just an implementation, that can get switched or updated out if/when the need arises. Once you start putting stuff in your view that is part of the application logic, you are traveling down a path of a maintenance headache, and then spaghetti code can quickly ensue.
Some people say, "Don't put any code in your view", I agree 99% of the time. I say "Don't put any domain/application/business logic in your view."
Whenever you're trying to put some code into your view ask yourself "If I switched from WPF to another framework would my app still work?" If the answer is no, then modify your ViewModel to incorporate what you were trying to put in your view.
First read Jose Answer if your answer to his question will be "yes" try this
XAML
<Window x:Class="icube.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Grid>
<ListBox Name="myLB" Margin="0,0,445,291"
SelectedItem="{Binding mySelectedItem, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding mySelectedIndex,Mode=OneTime}" >
<ListBox.ItemTemplate>
<!-- or what ever yourTemplate will be-->
<DataTemplate DataType="{x:Type SolidColorBrush}">
<Rectangle Width="20" Height="20" Fill="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Code-behind
using System.Windows;
using System.Windows.Media;
using System.Collections.ObjectModel;
namespace icube
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var obcoll = new ObservableCollection<SolidColorBrush>();
obcoll.Add(Brushes.Red);
obcoll.Add(Brushes.Green);
obcoll.Add(Brushes.Yellow);
obcoll.Add(Brushes.Blue);
obcoll.Add(Brushes.Orange);
myLB.ItemsSource = obcoll;
DataContext = new myClass();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//importent if you want to see your selected Element
myLB.ScrollIntoView(myLB.SelectedItem);
}
}
}
myClass
public class myClass
{
private SolidColorBrush _mySelectedItem = new SolidColorBrush();
public SolidColorBrush mySelectedItem
{
get { return _mySelectedItem; }
set { _mySelectedItem = value; }
}
public int mySelectedIndex
{
get { return 4; }
}
}
summary
like you can see the default SelectedItem get setted by the SelectedIndex which is bounded per OneTime Mode to mySelectedIndex. I also show here how you could get it IntoView
i how this will help somebody.
typically In MVVM your ObservableCollection of SolidBrushColor should be defined as a property in ViewModal. and this property should be bound to your ListBox. Now, to set default value you can have it in your ViewModal's constructor
If the property bound to SelectedItem is declared as dependency property then you can set the default value of the dependency propery to whatever you want.
public static readonly DependencyProperty SelectedColorProperty
= DependencyProperty.Register("SelectedColor", typeof(SolidBrushColor ),
typeof(<yourviewmodel>),
new UIPropertyMetadata(GetDefaultColor()));
Where GetDefaultColor() is a static method in your ViewModel,
which will return the required color from your ObservableCollection .

WPF Databinding in XAML

I have little problem with databinding in my current project.
I have an ObservableCollection I want to bind to an ListBox.
public ObservableCollection<GeoDataContainer> geoList = new ObservableCollection<GeoDataContainer>();
...later...
geoListBox.ItemsSource = geoList;
This code works fine. The Listbox has a datatemplate and everything looks perfect.
But I don't want to use C# code for binding. I want to make the binding in the XAML Code.
I am searching for days but I don't get it. These are two lines C# code but to archive this in XAML it seems impossible without creating my own class for my collection or adding a DataProvider or resources or whatever.
Is there no easy way to do it?
All you have to do is expose the collection and bind to it. For example, if you expose it as:
public ICollection<GeoDataContainer> GeoList
{
get { return geoList; }
}
You will be able to bind to it as:
<ListBox ItemsSource="{Binding GeoList}"/>
The "trick" is to make sure that the DataContext of the ListBox is the class that exposes the GeoList property.
Another good way would be instantiating geoList as a resource
<WindowResources>
<l:GeoCollection x:Key="geoList"/>
</WindowResources>
Then you have
GeoCollection geoList = FindResource("geoList") as GeoCollection;
Of course, this is for cases when the data is related to the view only. If this is related to model or modelview, you use DataContext and bind to its properties.
Kent suggestion is the way to go...
On a further note, if you do not wish to set your DataContext to the list, you can also retrieve the property with an another form of binding:
Make sure your root control has a name, i.e. "Root"
{Binding ElementName=Root, Path=GeoList}

Categories

Resources