C# & WPF Data Binding to ObservableCollection - c#

I came from a Linux heavy environment, where I wrote most of my tools in Python but now I am in a windows heavy environment, and need to share my tools with my team and some need to be GUI driven so I am trying to learn C#/WPF. I'm getting confused on Data Binding to an ObservableCollection in the code behind. I can get it work, but I don't understand why, which bothers me.
My code is simple, and I am literally just trying to get the basics working so I can move on to more complicated parts:
XAML:
<ListView x:Name="lvUrlList" HorizontalAlignment="Left" Height="441" Margin="15,62,0,0" VerticalAlignment="Top" Width="486" SelectionChanged="listView_SelectionChanged" ItemsSource="{Binding Path=urlList, ElementName=MainWindow1}">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding domain}"/>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code Behind:
namespace ReferalScraper
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
//public ObservableCollection<Url> urlList { get; set; } // Method 1
public ObservableCollection<Url> urlList = new ObservableCollection<Url>(); // Method 2
public MainWindow()
{
// urlList = new ObservableCollection<Url>(); // Method 1
InitializeComponent();
urlList.Add(new Url() { domain = "www.test1.com" });
}
public class Url
{
public string domain { get; set; }
}
private void button_Click(object sender, RoutedEventArgs e)
{
urlList.Add(new Url() { domain = "www.test2.com" });
urlList.Add(new Url() { domain = "www.test3.com" });
}
}
}
The uncommented method for creating and instantiating the ObservableCollection doesn't work, the code compiles but I get the output error:
System.Windows.Data Error: 40 : BindingExpression path error: 'urlList' property not found on 'object' ''MainWindow' (Name='MainWindow1')'. BindingExpression:Path=urlList; DataItem='MainWindow' (Name='MainWindow1'); target element is 'ListView' (Name='lvUrlList'); target property is 'ItemsSource' (type 'IEnumerable')
Which I understand that it means it can't find the urlList object in MainWindow. But I don't understand why it can't find it.
If I switch to the Method 1 and uncomment the following two lines (and comment out the "Method 2" part) it works fine:
public ObservableCollection<Url> urlList { get; set; }
...
public MainWindow(){
urlList = new ObservableCollection<Url>()
Why is declaring the ObserverableCollection with the { get; set } needed? I don't quite grasp why I can't just instantiate my ObservableCollection as an empty ObserverableCollection like I am in Method 2.
I'm feeling incredibly dense, and haven't quite been able to track down the right terminology to even get close to answering my questions. Can anyone explain to a not so bright fellow what I am missing?
I have a feeling this is some C# understanding that I am missing.

The { get; set; } syntax defines your uriList as a property (an auto-implemented property, in this case ). Without this, uriList is simply a field.
WPF Data Binding cannot bind to fields. See this related question for some further discussion as to why this is the case.
Generally in C# fields are not usually exposed as public, properties are preferred. This allows you to change the get/set implementation if required. As an aside, the naming convention for properties is PascalCased (so UriList rather than uriList).

You use ElementName binding when you try to get the Property you are binding, from another FramworkElement's property, which isn't the case here,
you need to first: properly set the DataContext either from the codebehind in the MainWindow constructor:
this.DataContext=this;
or from the Xaml in the Window
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Then declare you UriList as a Property so it can be used with binding
private ObservableCollection<Url> _uriList = new ObservableCollection<Url()
{
new Url() { domain = "www.test2.com" }
};
public bool UrlList
{
get
{
return _uriList;
}
set
{
if (_uriList == value)
{
return;
}
_uriList = value;
}
}
and change your binding to the following :
ItemsSource="{Binding UrlList}

Related

Order of setting the DataContext in the default constructor in WPF

I experiment with the order of setting the DataContext property in the default constructor in WPF.
<StackPanel>
<ListBox ItemsSource="{Binding MyItems, PresentationTraceSources.TraceLevel=High}"></ListBox>
<TextBlock Text="{Binding SomeText}"></TextBlock>
<TextBlock Text="{Binding SomeNum}"></TextBlock>
<TextBlock Text="{Binding Path=Person.Name}"></TextBlock>
<ListBox ItemsSource="{Binding Path=PersonList}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
1) With DataContext set before the InitializeComponent method
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string someText = "Default text";
public List<string> MyItems { get; set; }
public List<Person> PersonList { get; set; }
public Person Person { get; set; }
public int SomeNum { get; set; }
public string SomeText
{
get
{
return someText;
}
set
{
someText = value;
OnPropertyChanged("SomeText");
}
}
public MainWindow()
{
this.DataContext = this;
MyItems = new List<string>();
PersonList = new List<Person>();
Person = new Person();
InitializeComponent();
/*These changes are not reflected in the UI*/
SomeNum = 7;
Person.Name = "Andy";
/*Changes reflected with a help of INotifyPropertyChanged*/
SomeText = "Modified Text";
/* Changes to the Lists are reflected in the UI */
MyItems.Add("Red");
MyItems.Add("Blue");
MyItems.Add("Green");
MyItems[0] = "Golden";
PersonList.Add(new Person() { Name = "Xavier" });
PersonList.Add(new Person() { Name = "Scott" });
PersonList[0].Name = "Jean";
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public class Person
{
public string Name { get; set; } = "Default Name";
}
After the call to the InitializeComponent method changes to the values of properties are not reflected in the UI except for those properties which use INotifyPropertyChanged. Everything is clear so far.
However I noticed that changes to the list items are also reflected in the UI. How come?
I always thought that in order to reflect adding/removing from the collection I need ObservableCollection and to implement INotifyPropertyChanged on list object to detect modifications of these objects. What is the meaning of this?
2) With DataContext set after the InitializeComponent method
Why setting a DataContext property after the InitializeComponent is a bad practice with MVVM? Could you describe it more thoroughly or give a simple code example?
I always thought that in order to reflect adding/removing from the collection I need ObservableCollection<T> and to implement INotifyPropertyChanged on list object to detect modifications of these objects.
You do, if you want reliable updating of the UI during changes in the view model.
What is the meaning of this?
The "meaning" is that in your particular scenario, you are making assumptions that aren't valid. WPF components go through a variety of initialization steps, only some of which occur as part of the InitializeComponent() method.
If, for example, you were to move the code for your value updates into a handler for the Loaded event, you'd find some of the updates reflected in the UI, but not all.
If you move that same code into a method invoked via Dispatcher.InvokeAsync() using a priority of DispatcherPriority.SystemIdle, you'd find that none of the updates would be observed, except for the one backed by INotifyPropertyChanged. In that case, you're explicitly waiting until every aspect of initialization has completed, and there are no longer opportunities for the initialization code to observe your updated values.
It's all about timing. Any code that sets a value before the UI winds up observing it, can do so successfully without INotifyPropertyChanged or equivalent. But you're entirely at the mercy of the current implementation of the framework in that case. Different parts of the initialization happen at different times, and these are not all documented, so you're relying on undocumented behavior. It probably won't change, but you have no way to know for sure.
Why setting a DataContext property after the InitializeComponent is a bad practice with MVVM?
It's not. Don't believe everything you read, even (or especially!) on the Internet.
If you want to forego implementation of INotifyPropertyChanged, then it will be important that you initialize all of your view model data before assigning the DataContext. But, even if you assign the DataContext after calling InitializeComponent, that assignment will be observed (because DataContext is a dependency property and so provides property changed notification to the framework), and the UI will retrieve all of the bound data from your view model data.
What's important is that the view model data be initialized before the assignment of DataContext. Where that happens relative to InitializeComponent() is not important.
When a view model property does not fire the PropertyChanged event, its value must of course be set before assigning the view model instance to the view's DataContext.
It does however not matter if you assign the DataContext before or after calling InitializeComponent:
Given a Binding like
<TextBlock Text="{Binding SomeText}"/>
these two sequence will both result in showing the property value in the view:
DataContext = new { SomeText = "Hello, World." };
InitializeComponent();
and
InitializeComponent();
DataContext = new { SomeText = "Hello, World." };

WPF bindings do not pick up any changes

In my WPF application, I have some properties which I have bound to the XAML counterpart, but for some reason do not get set whenever their values change. I have implemented the INotifyPropertyChanged interface as well as set my DataContext for this View too, and it is still not picking up any changes.
I have this same pattern for other properties within this ViewModel which do work, while others don't.
Here is a snippet of my current code:
ViewModel
public class TestViewModel : INotifyPropertyChanged
{
private string testString;
public TestViewModel()
{
.....
this.RunCommand = new RelayCommand(this.RunAction);
}
public string TestString
{
get
{
return this.testString;
}
set
{
this.testString = value;
this.OnPropertyChanged("TestString");
}
}
private void RunAction()
{
.....
this.testString = "Running.";
}
}
View
<StatusBarItem>
<TextBlock Text="{Binding Path=TestString, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
</StatusBarItem>
DataContext (set in code-behind of another MainWindow class)
var testViewModel = SimpleIoc.Default.GetInstance<TestViewModel>();
var testWindow = new TestWindow() { DataContext = testViewModel };
testingWindow.Show();
If it helps, this is part of a multi-windowed application which uses MVVM-Light to pass properties between classes.
You are not changing the value of the TestString, you are assigning a command to change the value but you do not seem to be executing it.
this.RunCommand = new RelayCommand(this.RunAction);
Bind that command to something or execute it manually from somewhere.
Also you need to assign the property not the field
this.TestString = "Running.";
I found the problem. You are only updating the private property testString. But you do not update the property TestString so the notify is never called.
Try this:
this.TestString = "Running";

WPF: Binding TextBox Text to a sub-element of a Property with WCF?

I have a TextBox which I'm trying to bind to a element of a table property 'regimeAlias' is a column with the tbRegimes table which I have mapped with Entity Framework:
<TextBox Text="{Binding NewRegime.regimeAlias, Mode=TwoWay}"/>
Exposed property in my ViewModel:
private tbRegime _NewRegime;
public tbRegime NewRegime
{
get { return _NewRegime; }
set
{
_NewRegime = value;
OnPropertyChanged("NewRegime");
}
}
Lastly, here's the WCF Service Reference auto-generated code class:
public partial class tbRegime : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {
//blah blah blah
[System.Runtime.Serialization.DataMemberAttribute()]
public string regimeAlias {
get {
return this.regimeAliasField;
}
set {
if ((object.ReferenceEquals(this.regimeAliasField, value) != true)) {
this.regimeAliasField = value;
this.RaisePropertyChanged("regimeAlias");
}
}
}
The setter never gets hit. Is this because each element within the NewRegime object needs to raise PropertyChanged and if so is there an easy workaround without adding a further DTO layer to my code?
Edit3: with the post from your regimeAlias code. i have to say your binding should work. but of course if you wanna debug you have to set the breakpoint in your regimeAlias setter
<TextBox Text="{Binding NewRegime.regimeAlias, Mode=TwoWay}"/>
this code means, you bind to a Public Property regimeAlias in your class tbRegime.
your setter for NewRegime will never hit because you dont bind to it.
so check your tbRegime class property setter for regimeAlias.
EDIT: the DataContext of the TextBox is of course an object with the Public Property NewRegime, but like i said if you use dot notation in your binding the last property is the one you bind to :)
EDIT: you dont have much ways to workaround:) if you let the binding like you did, you need a model with a public property regimeAlias and it should implement INotifyPropertyChanged.
if you wanna wrap the regimeAlias Property then you have the problem the you have to raise OnPropertyChanged("MyRegimeAlias") at the right point.
public string MyRegimeAlias
{
get { return _NewRegime.regimeAlias; }
set
{
_NewRegime.regimeAlias = value;
OnPropertyChanged("MyRegimeAlias");
}
}
xaml
<TextBox Text="{Binding MyRegimeAlias, Mode=TwoWay}"/>

Quick way to link Objects from screen to View Model

Out of curiosity, is there a way to do this quicker without defining two string or objects?
Xaml
<TextBox Margin="5" Width="100" Text={Binding Path=dataString}></TextBox>
View Model
string _dataString;
public string dataString
{
get
{
return _dataString;
}
set
{
_dataString = value;
base.OnPropertyChanged();
}
}
You can define helpers to shorten the syntax somewhat. For example, if you use the MVVM Light Toolkit, and inherit your ViewModel from ViewModelBase, the toolkit provides a helper that enables use of the following syntax:
private string _dataString = null;
public string DataString
{
get { return _dataString; }
set { Set(ref _dataString, value); }
}
You still have to provide a backing field, but the helper takes care of notifying the exact property that changed, and only raises the event if the new value is in fact different from the current one.
You can also speed up the process of adding the properties by creating a custom code snippet and importing it into Visual Studio via the Code Snippets Manager.

Yet another databinding issue in silverlight

I know this topic has been discussed, but not by me yet. As I have seen on other examples about this issue, I am trying to create some basic custom DataPager UserControl. So that I did the following :
XAML:
<ComboBox Name="Size" ItemsSource="{Binding PageSourceSize}"
SelectedValue="{Binding PageSizePager}" />
With the following C#:
ObservableCollection<int> _PageSourceSize;
public ObservableCollection<int> PageSourceSize
{
get { return _PageSourceSize; }
set
{
_PageSourceSize = value;
RaisePropertyChanged("PageSourceSize");
}
}
public MyDataPager()
{
DataContext = this;
PageSizePager = 10;
PageSourceSize = new ObservableCollection<int>() { 10, 20, 50,100 };
}
public int PageSizePager
{
get { return (int)GetValue(PageSizePagerProperty); }
set { SetValue(PageSizePagerProperty, value); }
}
public static readonly DependencyProperty PageSizePagerProperty =
DependencyProperty.Register("PageSizePager", typeof(int), typeof(MyDataPager), new PropertyMetadata(10));
From here I intend to use my pager in a main UserControl :
<local:MyDataPager PageSizePager="20" x:Name="MyDataPager1" />
This works fine, but I would have liked to get the value from my viewModel using:
<local:MyDataPager PageSizePager="{Binding Path=PageSize,Mode=TwoWay}" x:Name="MyDataPager1" />
And the view model:
public int PageSize
{
get { return (int)GetValue(PageSizeProperty); }
set { SetValue(PageSizeProperty, value); }
}
public static readonly DependencyProperty PageSizeProperty =
DependencyProperty.Register("PageSize", typeof(int), typeof(ViewSchedeConsuntiviViewModel), new PropertyMetadata(10));
public MyViewModel()
{
PageIndex = 1;
PageSize = 20;
}
Could someone explain me why the binding between the view model and the user control does not work?
Looking on the code it seems that you have more then one PageSize properties defined in different classes. And most probabbly, it's difficult to understand just by looking on code provided, you bind in XAML one property, but change the value of another one, instead. Vary the name of one of PageSize properties to be sure where exactly databinding going to read/write.
I think this should help.
Working with a colleague of mine, we found a solution for what I intended to do :
In the Xaml of the MyDataPager usercontrol :
<Grid x:Name="LayoutRoot" Background="White" Loaded="MyDataPager_Loaded">
....
going with this definition of MyDataPager_Loaded :
void MyDataPager_Loaded(object sender, RoutedEventArgs e)
{
((Grid)sender).DataContext = this;
}
From the code above we have changed the ctor of the MyDataPager usercontrol to remove the datacontext binding :
public MyDataPager()
{
//DataContext = this;
Working this way, I am able to bind values in the main usercontrol like this :
<local:MyDataPager PageSizePager="{Binding Path=PageSize,Mode=TwoWay}" x:Name="MyDataPager1" />
So that the binding is made upon properties of the childusercontrol, not upon its control(ie: look of the child control may change without problems), and so that the child usercontrol does not have to use any "known" values from the datacontext of the main usercontrol.
Thanks for reading and for your support, it was greatly welcomed.I Hope these lines might serve another one in need of this.

Categories