Replace current binding source object with another object - c#

I'm totally new to C# and WPF and I'm trying to do my best with the data binding. I have a MyClass which implements INotifyPropertyChanged; so everytime I change a property value, this is updated in my UI. Then I have bound the DataContext of a stackpanel to an object of MyClass. Like this:
<StackPanel Name="stackPanel1" DataContext="{Binding}">
<TextBlock Name="textBlock1" Text="{Binding Path=Title, Mode=OneWay}" />
</StackPanel>
In code behind I do this:
item = new MyClass();
stackPanel1.DataContext = item;
and the binding is working fine. If I replace my current binding source object with another one, I have to manually set this by typing again the datacontext binding:
item = new MyClass();
stackPanel1.DataContext = item;
item1 = new MyClass();
.
. //manipulate item1
.
if (item1 is ok)
item=item1;
stackPanel1.DataContext = item;
Is there a better way to replace my source object and have all the associated bindings updated?

When you say stackPanel1.DataContext = item;, you are setting the property, not binding it.
When you set the property, you are setting it equal to an instance of the object. When you bind it, you are telling it it will be getting its value from some other location, so look in that location anytime it needs to get the value.
Providing your class that contains the bound properties implements INotifyPropertyChanged, then the UI will be alerted anytime a bound property changes, which causes the binding to get reevaluated.
For example, if you had set the DataContext initially with
MyWindow.DataContext = this;
where this was your Window, and your Window had a propety of type MyClass called Item, then you could bind the DataContext using the following
<StackPanel DataContext="{Binding Item}" ...>
and anytime you updated the property Item, your StackPanel's DataContext would also update (providing you implement INotifyPropertyChanged).
If you're interested, I like to blog about beginner concepts in WPF, and you may be interested in checking out my article What is this "DataContext" you speak of?, which is a very simple explanation of what the DataContext is and how it's used.
To summarize, WPF has two layers: the UI layer and the Data Layer. The DataContext is the data layer, and when you write {Binding SomeProperty}, you are actually binding to the data layer. Typically you set the data layer (DataContext) once in your code behind, and then use Bindings in your XAML to make your UI layer display information from the data layer.
(You may also be interested in checking out my Simple MVVM Example, which contains a very simple working code sample, and illustrates some examples of how INotifyPropertyChanged is implemented and how the UI layers and Data layers can be completely separate)

You may add a CurrentItem property in your MainWindow (or UserControl or whatever it is) and also implement INotifyPropertyChange for that property. Then set
DataContext = this;
in the MainWindow's constructor and bind like this:
Text="{Binding Path=CurrentItem.Title}"
Now whenever you set
var item = new MyClass();
...
CurrentItem = item;
the binding will be updated.

DataContext="{Binding}"
and
stackPanel1.DataContext = item;
Both do basically the same thing. The difference being that one is done in XAML and the other is in code. While the first example would allow binding to be updated given a binding parent the second one must be updated every time you want to change what the stackpanel is attached to. IMHO you should create a common binding parent to bind against. This would allow you to change the child bindings without having to set the context everytime.
<StackPanel Name="parentPanel">
<StackPanel Name="stackPanel1" DataContext="{Binding Path=Child}">
<TextBlock Name="textBlock1" Text="{Binding Path=Title, Mode=OneWay}" />
</StackPanel>
</StackPanel>
parent = new ParentClass();
parent.Child= new MyClass();
parentPanel.DataContext = parent ;
Now if notify property changed was created on ParentClass correctly you can changing the binding for the child stack panel
parent.Child= new NewClass();

Related

Accessing a static property of a view model is null

in my app I need to call a method in my main view model from a sub settings view model to change a tab control in the main view. Basically its a list of viewsmodels in a tab control. The settings viewmodel has checkboxes to turn tabs on or off.
Anyway, I setup a static property in the main viewmodel of the instance. In my settings viewmodel constructor I get a null response but if I retrieve the instance on each checkbox property change it works. Seems like a timing issue on when the instances are created. Is there an event or something that can tell me when the main viewmodel instance isn't null?
<Window.Resources>
<DataTemplate DataType="{x:Type skyTelescope:SkyTelescopeVM}">
<skyTelescope:SkyTelescopeV />
</DataTemplate>
<DataTemplate DataType="{x:Type rotator:RotatorVM}">
<rotator:RotatorView />
</DataTemplate>
<DataTemplate DataType="{x:Type focuser:FocuserVM}">
<focuser:FocuserView />
</DataTemplate>
<DataTemplate DataType="{x:Type settings:SettingsVM}">
<settings:SettingsV />
</DataTemplate>
</Window.Resources>
There's a lot to learn with wpf and the mvvm approach is very different from event driven code.
Your markup looks like you're doing viewmodel first... or something like that.
If you have a MainWindowViewModel exposes say a SubVM property.
Bind SubVM to the content property of a contentcontrol in mainwindow.
Set SubVM to an instance of a viewmodel such as SettingsVM.
This is then templated out in the view using the matching datatemplate.
You see a SettingsV appear.
The datacontext of SettingsV is your SubVM.
Bind controls in SettingsV to properties in SubVM and the values can transfer between them.
That allows you to control when you instantiate SettingsVM in MainWindowViewModel.
You therefore "know" whether you have an instance of it or not.
You can cache instances of viewmodels in a dictionary.
Use a type as your key and you can instantiate one if you don't have it in your dictionary, then re-use it if you wanted to retain state.
You can make MainWindow instantiate MainWindowViewModel by defining it's datacontext in xaml.
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
With viewmodel first there's an oddity with the way templating works. If you wanted to reset (unbound) view state then you need to force the re-templating by setting your SubVM property null first.
This command takes a Type as a parameter
private RelayCommand<Type> navigateCommand;
public RelayCommand<Type> NavigateCommand
{
get
{
return navigateCommand
?? (navigateCommand = new RelayCommand<Type>(
vmType =>
{
CurrentViewModel = null;
CurrentViewModel = Activator.CreateInstance(vmType);
}));
}
}
Obviously, this doesn't stash any instance of a vm away in a dictionary, it's just instantiating every time.

UWP Binding property to current item of ItemsSource

I got the following setup for my page:
<ListView ItemsSource="{Binding RecentResults}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid Background="Transparent" Orientation="Horizontal" ItemWidth="400" ItemHeight="300" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<ui:CarResultControl Result="{Binding}" Padding="0" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I bind to a local property which provides my results asynchronously. This works perfectly fine.
Now my CarResultControl binds to it's local Result property (which is an DependencyProperty). When I specify Result={Binding} then the property get's updated but not as expected with the CarResult but CarResultControl. If I use Result={Binding Path=.} or similiar, it just doesn't update at all.
As far as I'm used to it in WPF {Binding} actually bound to the data object itself, not the control you´re attaching the binding to. Interesting is that IntelliSense actually shows the properties of the expected CarResult object when I have to choose a binding path for Result.
Is UWP doing it's own thing here again, am I just a fool and doing it wrong or is this really a bug? I struggled for long enough and can't find any info about it.
This actually should be working the way you expect unless you are changing the DataContext of the CarResultControl.
UserControls actually work in a bit weird way - when you set this.DataContext = this in the constructor, then the binding on properties will actually be relative to the UserControl itself.
To fix this you must make sure not to change the DataContext of the control itself. The easiest way is to add x:Name to the root Grid (or whatever control you have in the content of the UserControl and then set its DataContext:
RootGrid.DataContext = this;
This way you will the data context of the RootGrid will be set to the control itself, but the user control's properties will be bound to the data context relative to where the user control is, here to the list item.

Dependency Property Datacontext

I have a usercontrol, and there is a Datacontext set for it. This usercontrol contains also a Dependency-Property. Now, i want simply bind to this property.
I think the problem has something to do with the wrong datacontext.
The dependency-Property in my usercontrol (called TimePicker) looks like this:
public TimeSpan Time
{
get { return (TimeSpan)GetValue(TimeProperty); }
set
{
SetValue(TimeProperty, value);
OnPropertyChanged();
}
}
public static readonly DependencyProperty TimeProperty = DependencyProperty.Register("Time", typeof (TimeSpan), typeof (TimePicker));
I try to use it like this:
<upDownControlDevelopement:TimePicker Grid.Row="1" Time="{Binding Path=TimeValue}" />
When i do this i get the following binding error:
System.Windows.Data Error: 40 : BindingExpression path error: 'TimeValue' property not found on 'object' ''TimePicker' (Name='TimePickerControl')'. BindingExpression:Path=TimeValue; DataItem='TimePicker' (Name='TimePickerControl'); target element is 'TimePicker' (Name='TimePickerControl'); target property is 'Time' (type 'TimeSpan')
Any help would be highly appreciated
Greetings Michael
PS: you can download the code at here
Although this has now been solved there seems to be some, in my opinion, inappropriate use of the DataContext.
When developing a custom reusable control, you should not set DataContext at all. What the DataContext will be, that is for the user of the control to decide, not for the developer. Consider the following common pattern of code:
<Grid DataContext="{Binding Data}">
<TextBox Text="{Binding TextValue1}" />
<!-- Some more controls -->
</Grid>
Notice that here, you are using the Grid control. The developer of the control (in this case, the WPF team), didn't touch the DataContext at all - that is up to you. What does it mean for you as a control developer? Your DependencyProperty definition is fine, but you shouldn't touch the DataContext. How will you then bind something inside your control to the DependencyProperty value? A good way is using a template (namespaces omitted):
<MyTimePicker>
<MyTimePicker.Template>
<ControlTemplate TargetType="MyTimePicker">
<!-- Stuff in your control -->
<TextBlock Text="{TemplateBinding Time}" />
<TextBox Text="{Binding Time, RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
<MyTimePicker.Template>
</MyTimePicker>
Note that TemplateBinding is always one-way only, so if you need any editing at all, you need to use normal binding (as you can see on the TextBox in the example).
This only means that the TextBlock/Box inside your control will get its Time value from your custom control itself, ignoring any DataContext you might have set.
Then, when you use the control, you do it like this (added to my first example):
<Grid DataContext="{Binding Data}">
<TextBox Text="{Binding TextValue1}" />
<!-- Some more controls -->
<MyTimePicker Time="{Binding TimeValue}" />
</Grid>
What just happened here is that the MyTimePicker does not have DataContext set anywhere at all - it gets it from the parent control (the Grid). So the value goes like this: Data-->(binding)-->MyTimePicker.Time-->(template binding)-->TextBlock.Text.
And above all, avoid doing this in the constructor of your custom control:
public MyTimePicker()
{
InitializeComponent();
DataContext = this;
}
This will override any DataContext set in XAML, which will make binding a huge pain (because you'll have to always set Source manually). The previous example would not work, and this wouldn't work either:
<MyTimePicker DataContext="{Binding Data}" Time="{Binding TimeValue}" />
You would think this is OK, but the DataContext will be resolved in the InitializeComponent() call, so the value will be immediately overwritten. So the binding to TimeValue will look for it in the control instead (which will, of course, fail).
Just don't touch the DataContext when developing a control and you'll be fine.
You don't need to override the data context of user control. You can use RelativeSource to point your binding source property i.e. TimeValue to any other source you like. E.g. If you have the source property in your window's class. You could simply point your binding target to the source in window's data context as follows:
{Binding Path=DataContext.TimeValue, RelativeSource={ RelativeSource AncestorType={x:Type Window}}}
Your error states that 'TimeValue' property not found on 'object' 'TimePicker', which means that the WPF Framework is looking at the 'TimePicker' object to resolve the 'TimeValue' property value. You must have somehow set the DataContext of the Window or UserControl that contains the 'TimePicker' object to an instance of the 'TimePicker' object.
Instead, it should be set to an instance of the class that declares the 'TimeValue' property. If you're using a view model, then you should set it to an instance of that:
DataContext = new YourViewModel();
If the 'TimeValue' property is declared in the Window or UserControl then you can set the DataContext to itself (although generally not recommended):
DataContext = this;
Please note that when data binding to the 'Time' property from inside your TimePicker control, you should use a RelativeSource Binding:
<TextBlock Text="{Binding Time, RelativeSource={RelativeSource
AncestorType={x:Type YourLocalPrefix:TimePicker}}}" ... />
Normally we are not setting datacontext directly.If u want to set datacontext create an instance of your usercontrol and set datacontext individually to each one.

How can I bind a property?

I'm using the Bing map SDK in my WPF application and the XAML looks like:
<m:Map
x:Name="MyMap"
Grid.Row="1"
CredentialsProvider="KEY"
ZoomLevel="{BINDING MapZoomLevel}"
Mode="Road">
The code behind:
private int mapZoomLevel;
public int MapZoomLevel { get { return mapZoomLevel; } set { mapZoomLevel = value; NotifyOfPropertyChange(() => MapZoomLevel); } }
But this aint working. I guessing it is because I've already bound the Map by setting x:Name. The problem is that I can't remove the x:Name since I'm doing some setup in the view but is there a workaround? I would like to be able to bind the ZoomLevel of the map somehow
In order to data bind, you need to do a few things:
1) You must set the DataContext of the UserControl or Window to the object that contains the property that you want to bind to. That could be like this (in the UserControl or Window code behind) if that object is a separate view model class:
DataContext = new SomeTypeOfViewModel();
Or like this if the property is declared in the code behind:
DataContext = this;
2) You must implement the INotifyPropertyChanged interface or implement DependencyPropertys - you seem to have implemented the INotifyPropertyChanged interface, but you must ensure that you have done it correctly.
3) You must provide a valid Binding Path... BINDING is not valid, so an appropriate Binding Path for you might be this (depending on where you have declared your property):
<m:Map x:Name="MyMap" Grid.Row="1" CredentialsProvider="KEY"
ZoomLevel="{Binding MapZoomLevel}" Mode="Road">
Please read the Data Binding Overview page on MSDN for the full story.
based on your tags you are using Caliburn Micro with this? Datacontext is already set with viewmodel/view from the framework. ZoomLevel="{Binding MapZoomLevel, Mode=TwoWay}" is required.

Binding ObservableCollection to ListView

I've been following this tutorial to try to work an understanding of XML, WPF, and C# (coming out from Fortran). I've gotten the functionality working (thanks to the tutorial), but I'm having troubles modifying it to work with WPF instead of WinForms.
Essentially, when I click the "Get RSS" button, the following is happening:
RssManager reader = new RSSManager();
ObservableCollection<Rss.Items> _list = new ObservableCollection<Rss.Items>();
reader.Url = txtFeed.Text;
reader.GetFeed();
_list = (ObservableCollection<Rss.Items>)reader.RssItems;
The listview just sits blank. It's code is like the following. Also, trying this with a listbox results in the name of the class being populated for each item in the list instead of my data:
<ListView ItemsSource="_rssItems">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Title}"/>
</GridView>
</ListView.View>
</ListView>
Would it be possible to do something like (again, forgive me for my ignorance, here):
_list.Add( new Rss.Items());
The list (_list) contains all of the information that I need, I just want to figure out how to properly bind it (or add it) to the ListView.
It looks like you are a bit lost.
Ultimately you want to bind your view(WPF form) to a View-Model and your View-Model to a model (the RSSManager).
For now lets bind the view directly to the model.
In your constructor you make a new instance of the model and you assign it to the data context.
This model is going to live as long as the form -
public MainWindow()
{
InitializeComponent();
_model = new RssManager();
DataContext = _model;
}
Then in your XAML you bind the item source to your collection property :
<ListView ItemsSource="{Binding Path=RssItems}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Title}"/>
</GridView>
</ListView.View>
</ListView>
Note that in "Path=RssItems" is relative to whatever you assigned to the DataContext.
Then in your refresh button logic you call:
_model.Url = txtFeed.Text;
_model.GetFeed();
What you ultimately want to do is put another layer in the middle. This is the view model. The View model is as you may have guessed a model of the view.
The view model's job is to collect information about the state of the view and to expose the data from the model that is to be presented by the view. It also can hold current ui state information - f.e. which row in the table is selected, since some command may act on that later. In essence it allows to abstract all the logic of the view from your code. Your commands operate on things like which row is selected - regardless of which type of control did the selection.
As Lee suggests in his comment, the binding for the ItemsSource property of the ListView does not appear to be correct. There are multiple ways to approach this, depending on how your project is designed/structured.
In order to DataBind, the ListView will need some kind of DataContext which (as the name implies) is kind of the Context for the Binding. If you are using MVVM, then most likely, the DataContext of your entire Window/Control would be a ViewModel. In that case, you bind to a property of the ViewModel like this:
<ListView ItemsSource="{Binding RssItems}">...</ListView>
This assumes you have a ViewModel with a public RssItems property (which is some kind of List/Enumerable) and the ViewModel is DataContext.
If you are not using MVVM, there are a lot of ways to assign the ItemsSource both with DataBinding and without. The easiest way I can suggest, if you're not fully comfortable with DataBinding, would be to manually assign the ItemsSource, like this:
Xaml:
<ListView x:Name="MyRssList">...</ListView>
Code Behind (somewhere after the UI has Loaded and after you've created/populated _list):
MyRssList.ItemsSource = _list;
This doesn't use DataBinding, but it will get the job done. If you want to start out with DataBinding, you could do the following:
XAML:
<ListView x:Name="MyRssList" ItemsSource="{Binding}>...</ListView>
Code Behind:
MyRssList.DataContext = _list;
This will assign the List as the DataContext of the ListView, then DataBind the ItemsSource property to the DataContext.
Overall, You're on the right track. I'd recommend some reading on DataBinding and MVVM. MVVM is a very good way to leverage the powerful DataBinding capabilities of WPF, and a strong understanding of DataBinding is extremely valuable in designing and building great WPF apps.
Hope this helps. Good luck!

Categories