I have a Silverlight page that gets its data from a view model class which aggregates some data from various (RIA services) domain services.
Ideally I'd like the page to be able to data bind its controls to properties of the view model object, but because DomainContext.Load executes a query asynchronously, the data is not available when the page loads.
My Silverlight page has the following XAML:
<navigation:Page x:Class="Demo.UI.Pages.WidgetPage"
// the usual xmlns stuff here...
xmlns:local="clr-namespace:Demo.UI.Pages" mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
d:DataContext="{d:DesignInstance Type=local:WidgetPageModel, IsDesignTimeCreatable=False}"
d:DesignWidth="640" d:DesignHeight="480"
Title="Widget Page">
<Canvas x:Name="LayoutRoot">
<ListBox ItemsSource="{Binding RedWidgets}" Width="150" Height="500" />
</Canvas>
</navigation:Page>
My ViewModel looks like this:
public class WidgetPageModel
{
private WidgetDomainContext WidgetContext { get; set; }
public WidgetPageModel()
{
this.WidgetContext = new WidgetDomainContext();
WidgetContext.Load(WidgetContext.GetAllWidgetsQuery(), false);
}
public IEnumerable<Widget> RedWidgets
{
get
{
return this.WidgetContext.Widgets.Where(w => w.Colour == "Red");
}
}
}
I think this approach must be fundamentally wrong because the asynchronous nature of Load means that the widget list is not necessarily populated when the ListBox data binds. (A breakpoint in my repository shows that the code to populate to collection is being executed, but only after the page renders.)
Can someone please show me the right way to do this?
The missing piece of the puzzle is that I needed to be raising events when the properties changed.
My updated ViewModel is as follows:
public class WidgetPageModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private WidgetDomainContext WidgetContext { get; set; }
public WidgetPageModel()
{
this.WidgetContext = new WidgetDomainContext();
WidgetContext.Load(WidgetContext.GetAllWidgetsQuery(),
(result) =>
{
this.RedWidgets = this.WidgetContext.Widgets.Where(w => w.Colour == "Red");
}, null);
}
private IEnumerable<Widget> _redWidgets;
public IEnumerable<Widget> RedWidgets
{
get
{
return _redWidgets;
}
set
{
if(value != _redWidgets)
{
_redWidgets = value;
RaisePropertyChanged("RedWidgets");
}
}
}
}
The controls bound to these properties are updated when the property change event fires.
Related
I'm trying to make a control that has a current value with an optional equation string.
I have 2 textboxes:
One (a) where you can enter an equation shortcut to a value to put into the other (b).
(b) contains the actual value.
(for example, in (a), if you enter 'pi', the second will then fill with "3.1415926535897931")
I'm using 2 textboxes so the user can refine their equation if they need to, and watch the value change as they modify it.
The data has 2 fields, one being the equation string and the other being the current value.
so I have (a).Text bound to the string, a new property on (a) that holds the value, and I bind (b).Text to the value also.
(a).Text is TwoWay
(a).Value is OneWayToSource (since changes to the text should only be pushed to b)
(b).Value is TwoWay
This all works fine if I have the data set in the constructor before any XAML binding, but does not work at all if I add the data after binding.
Here is a minimal amount of code that shows the problem.
The only comment is at the line that can make it work or not.
As a last resort I could turn it into a custom control and handle it in the code-behind, but I'd think this should work in the first place.
Any ideas why this isn't working?
Thanks!
Here is the XAML:
<Window x:Class="twoBindingsOnSameField.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:twoBindingsOnSameField"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button Content="load data" Click="Button_Click" Width="80" IsEnabled="{Binding NeedsData}"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="enter text:" Width="80"/>
<local:TextBoxCalc Text="{Binding Item.ItemString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextBoxCalculatedValue="{Binding Item.ItemValue, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
Width="200"
IsEnabled="{Binding HasData}"
/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="updated text:" Width="80"/>
<TextBox Text="{Binding Item.ItemValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="200"
IsEnabled="{Binding HasData}"
/>
</StackPanel>
</StackPanel>
</Window>
Here is the codebehind.
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace twoBindingsOnSameField
{
public partial class MainWindow : Window
{
data data;
public MainWindow()
{
InitializeComponent();
data = new data();
/// ---- Does not work with the following line commented out, but does if it is uncommented ----
/// ---- use the button to set the data ----
//setdata();
DataContext = data;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
setdata();
}
void setdata()
{
if (data.Item == null)
data.Item = new dataitem();
}
}
public class data : notifybase
{
dataitem item;
public data()
{
}
public dataitem Item
{
get
{
return item;
}
set
{
if (item != value)
{
item = value;
notifyPropertyChanged("Item");
notifyPropertyChanged("HasData");
notifyPropertyChanged("NeedsData");
}
}
}
public bool HasData
{
get
{
return Item != null;
}
}
public bool NeedsData
{
get
{
return Item == null;
}
}
}
public class dataitem : notifybase
{
string itemString;
string itemValue;
public dataitem()
{
itemString = "3";
itemValue = "4";
}
public virtual string ItemString
{
get
{
return this.itemString;
}
set
{
if (!object.Equals(this.itemString, value))
{
this.itemString = value;
notifyPropertyChanged("ItemString");
}
}
}
public virtual string ItemValue
{
get
{
return this.itemValue;
}
set
{
if (!object.Equals(this.itemValue, value))
{
this.itemValue = value;
notifyPropertyChanged("ItemValue");
}
}
}
}
public class TextBoxCalc : TextBox
{
public TextBoxCalc()
{
TextProperty.AddHandler(this, (o,e)=>TextBoxCalculatedValue="updated:" + Text);
}
#region TextBoxCalculatedValue
public static DependencyProperty TextBoxCalculatedValueProperty = DependencyProperty.Register("TextBoxCalculatedValue", typeof(string), typeof(TextBoxCalc), new PropertyMetadata(""));
public string TextBoxCalculatedValue
{
get
{
return (string)GetValue(TextBoxCalculatedValueProperty);
}
set
{
if (!object.Equals(TextBoxCalculatedValue, value))
SetValue(TextBoxCalculatedValueProperty, value);
}
}
#endregion
}
public class notifybase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
protected virtual void notifyPropertyChanged(string propertyName)
{
PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
OnPropertyChanged(e);
}
}
static class extensions
{
public static void AddHandler(this DependencyProperty prop, object component, EventHandler handler)
{
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(prop, component.GetType());
if (dpd != null)
dpd.AddValueChanged(component, handler);
}
}
}
The reason why it works when you uncomment //setdata(); is because it is initializing the object in what is effectively your viewmodel, therefore you can change its properties via binding. To clarify as a side note, data would be your view model, and dataitem is your model, however you're dataitem is using INPC, so it doesn't really make sense in this case to have a viewmodel necessarily.
Anyways, the issue is that TextBoxCalculatedValue is set to a OneWayToSource binding. When you run the code commented out, its going to try and bind to a null value. When it does, it tries to update a null value, which isn't possible. WPF handles what would normally be a null exception automatically. When you update the dataItem by clicking the button, it doesn't update the object TextBoxCalc is bound to, so instead, it will continue trying to bind & update the null object. Change it to a TwoWay binding and you'll see a difference. Changing to TwoWay is probably your best option.
Good practice is to use constructor injection to practice dependency injection. With that being said, passing a dataItem to data would be the best route, and at the very least, initializing dataItem in data's constructor would be an ideal approach. So,
public data(dataItem item)
{
Item = item;
}
or
public data()
{
Item = new dataitem();
}
I'm using MVVMlight in windows phone. The model is bound via xaml through the locator. The view model loads a new instance of the model from a webrequest then assigns it. I'm not sure why the view is not being updated, is it because its a new instance being assigned? If I update a property of the model instead of assigning a new instance, it updates on the view.
How do I update the view when assigning a new instance of the model?
View Model:
public class MyViewModel : ViewModelBase
{
public MyModel Model { get; set; }
public MyViewModel ()
{
if (IsInDesignMode)
{
}
else
{
//async call
api.GetModel(response =>
Deployment.Current.Dispatcher.BeginInvoke
(() =>
{
//this works.
//Model.Property1 = "Some Text";
//this doesn't work
Model = response.Data;
}
));
}
}
}
View.xaml
<UserControl x:Class="Grik.WindowsPhone.CardDetailsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
DataContext="{Binding MyModel, Source={StaticResource Locator}}" >
<Grid x:Name="LayoutRoot" >
<TextBlock Text="{Binding Model.Property1}" />
</Grid>
</UserControl>
I would like to see your code in View Model Locator class
In MyViewModel class you may need to rewrite this property
public MyModel Model { get; set; }
to
private MyModel _model;
public MyModel Model {
get{return this._model;}
set{
this._model = value;
this.NotifyPropertyChanged("Model");
}
}
the ViewModel does not implement INotifyPropertyChanged interface.
The MyModel as well as all of its properties need to use the property changed event as well.
You show you use Model.Property1 in the xaml, Model.Property1 needs to also raise the property changed event.
public class MyModel : INotifyPropertyChanged
{
private string _Property1 = "";
public string Property1
{
get { return this._Property1; }
set
{
if (value != this._Property1)
{
this._Property1 = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I have the following simple WPF-app:
<Window x:Class="TabControlOutOfRangeException.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">
<TabControl ItemsSource="{Binding ItemsSource}"
SelectedIndex="{Binding SelectedIndex, IsAsync=True}" />
</Window>
with following simple code-behind:
using System.Collections.Generic;
namespace TabControlOutOfRangeException
{
public partial class MainWindow
{
public List<string> ItemsSource { get; private set; }
public int SelectedIndex { get; set; }
public MainWindow()
{
InitializeComponent();
ItemsSource = new List<string>{"Foo", "Bar", "FooBar"};
DataContext = this;
}
}
}
When I click on the second tab ("Bar"), nothing is displayed. When I click again on any tab, I get an IndexOutOfRangeException. Setting IsAsync to False, the TabControl works.
Unfortunately, I have the requirement to query the user a "Save changes?" question when he leaves the current tab. So I wanted to set the SelectedIndex back to the old value within the set-property. Obviously this doesn't work. What am I doing wrong?
Update
I've subclassed the TabControl with the evil hack and it works for me. Here is the code of MainWindow.xaml:
<Window x:Class="TabControlOutOfRangeException.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:TabControlOutOfRangeException="clr-namespace:TabControlOutOfRangeException" Title="MainWindow" Height="350" Width="525">
<Grid>
<TabControlOutOfRangeException:PreventChangingTabsTabControl
ItemsSource="{Binding ItemsSource}"
SelectedIndex="{Binding SelectedIndex}"
CanChangeTab="{Binding CanChangeTab}" Margin="0,0,0,51" />
<CheckBox Content="CanChangeTab" IsChecked="{Binding CanChangeTab}" Margin="0,287,0,0" />
</Grid>
</Window>
And here MainWindow.xaml.cs:
using System.Collections.Generic;
using System.ComponentModel;
namespace TabControlOutOfRangeException
{
public partial class MainWindow : INotifyPropertyChanged
{
public int SelectedIndex { get; set; }
public List<string> ItemsSource { get; private set; }
public MainWindow()
{
InitializeComponent();
ItemsSource = new List<string> { "Foo", "Bar", "FooBar" };
DataContext = this;
}
private bool _canChangeTab;
public bool CanChangeTab
{
get { return _canChangeTab; }
set
{
_canChangeTab = value;
OnPropertyChanged("CanChangeTab");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(property));
}
}
}
And finally the subclassed TabControl:
using System;
using System.Windows;
using System.Windows.Controls;
namespace TabControlOutOfRangeException
{
public class PreventChangingTabsTabControl : TabControl
{
private int _previousTab;
public PreventChangingTabsTabControl()
{
SelectionChanged += (s, e) =>
{
if (!CanChangeTab)
{
e.Handled = true;
SelectedIndex = _previousTab;
}
else
_previousTab = SelectedIndex;
};
}
public static readonly DependencyProperty CanChangeTabProperty = DependencyProperty.Register(
"CanChangeTab",
typeof(Boolean),
typeof(PreventChangingTabsTabControl)
);
public bool CanChangeTab
{
get { return (bool)GetValue(CanChangeTabProperty); }
set { SetValue(CanChangeTabProperty, value); }
}
}
}
I'd consider a redesign of that window instead of introducing a heap of new problems by just trial-and-erroring on the "IsAsync" property of the binding.
I am not sure if a tab control will allow this level of control you seek. You could try to catch the event when someone tries to change the selected item, but you would not be able to cancel it out. There is a way however, see Option 4 if you dont want to read the other suggestions.
Option 1: The custom control
I would consider writing a bit of custom code that mimics the functionality of an item container. Its easy to achieve your desired behaviour this way. Just bind a command to the buttons (or whatever control you wish the user to click on), and return CanExecute with false if there are still changes to be submitted - or ask your user whatever you want when it gets executed, and only change the content displayed (ie your custom "TabItem") if desired.
Option 2: Preventing the user by disabling the tabs
Another way would be to bind the "IsEnabled" property of each of the tabitems to a dependency property on your viewmodel, that controls which of them is available to the user. Like, you know that the first page still needs work, just disable all the other ones meanwhile. But be aware that right now you are not creating any TabItems - your content are just plain strings.
public List<TabItem> ItemsSource { get; private set; }
....
ItemsSource = new List<TabItem> { new TabItem() { Header = "Foo", Content = "Foo" }, new TabItem() { Header = "Bar", Content = "Bar" }, new TabItem() { Header = "FooBar", Content = "FooBar" } };
Since you don't want to prevent the user doing something but rather would like to ask to save the changes, i'd go for the custom control route. Still there is option 3.
Option 3: Popup window
Use a popup window and ask to save changes if the user is finished with changing whatever is on that page and clicks on the "Close" button (rather than the "Save" button that should also reside on the same page ;) )
Option 4: Check on StackOverflow
Actually i did that for you, and here is a solution another user has found for the exact same problem: WPF Tab Control Prevent Tab Change
The reason why i didnt post that up-front was that i personally wouldnt do it that way because, man do i HATE applications that do this.
Here you go.
Try actually implementing the SelectedIndex
namespace TabControlOutOfRangeException
{
public partial class MainWindow
{
public List<string> ItemsSource { get; private set; }
private int selectedIndex
public int SelectedIndex {
get { return selectedIndex; }
set { selecectedIndex = value; } }
public MainWindow()
{
InitializeComponent();
ItemsSource = new List<string>{"Foo", "Bar", "FooBar"};
DataContext = this;
}
}
}
If you want to be able to affect the TabControl the binding needs to be two-way, i.e. your code-behind needs to be able to notify the view that the property changed, for that you should implement INotifyPropertyChanged in your window, e.g.
public partial class MainWindow : INotifyPropertyChanged
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
if (_selectedIndex != value)
{
_selectedIndex = value;
OnPropertyChanged("SelectedIndex");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Async bindings are usually for properties which have a long-running getter, with e.g. a database query, you should not need this here.
In case you want to to change the selectedIndex in the setter itself, then to get it updated on UI, you have to raise the property changed in an async manner like this -
public partial class MainWindow : INotifyPropertyChanged
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
if (_selectedIndex != value)
{
_selectedIndex = value;
OnPropertyChangedAsAsync("SelectedIndex");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
protected virtual void OnPropertyChangedAsAsync(string propertyName)
{
Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate { OnPropertyChanged(propertyName); }, DispatcherPriority.Render, null);
}
I have a mvvm(model view viewmodel) silverlight application that has several views that need to be loaded into ContentControls (i made it all in expression blend). What i dont know how to do is, for example, to load one view (user control) in one content control by clicking a button from another view that is in another content control. To make it easier to understand the problem, i need to do something similar to this:
http://www.codeproject.com/KB/silverlight/BlendableVMCom.aspx
with that difference that child1 and child2 are supposed to be loaded into theirown content controls by clicking Call child1 or call child2 buttons.
and example would be appreciated. Thanks in advance!
This example is very simplified, but I think you now how to adjust it to your application.
The main view:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border x:Name="commandsView">
<Button Content="Call view 1" Command="{Binding CallView1Command}" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="5" />
</Border>
<Border x:Name="displayedView" Grid.Column="1">
<ContentControl Content="{Binding CurrentView}" />
</Border>
</Grid>
I haven't created separated views as user controls, here are just borders, which can be replaced by real views.
Different view models for different views in code behind:
this.commandsView.DataContext = new CommandsViewModel();
this.displayedView.DataContext = new DisplayedViewModel();
First view model conains the command which sends the message to another view model:
public class CommandsViewModel
{
public CommandsViewModel()
{
this.CallView1Command = new RelayCommand(() =>
Messenger.Default.Send<View1Message>(new View1Message()));
}
public RelayCommand CallView1Command { get; set; }
}
public class View1Message : MessageBase
{
}
To make this example work, download the MVVM Light library.
The second view model receive the message and creates a view for its property:
public class DisplayedViewModel : ViewModelBase
{
public DisplayedViewModel()
{
Messenger.Default.Register<View1Message>(this, obj =>
this.CurrentView = new TextBlock { Text = "Pressed the button 1 and now here is the view 1" });
}
private object currentView;
public object CurrentView
{
get { return currentView; }
set
{
currentView = value;
RaisePropertyChanged("CurrentView");
}
}
}
Again, it is possible to use clr object instead of controls and apply data templates in xaml, but there will not be enough space to provide all the resulting code.
So that is all, the main idea is a some kind of event aggregator, which is the Messenger class in this particular case.
Without the MVVM Light it will require more code:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
var events = new GlobalEvents();
this.commandsView.DataContext = new CommandsViewModel(events);
this.displayedView.DataContext = new DisplayedViewModel(events);
}
}
public class GlobalEvents
{
public event EventHandler View1Event = delegate { };
public void RaiseView1Event()
{
View1Event(this, EventArgs.Empty);
}
}
/// <summary>
/// Commands which call different views
/// </summary>
public class CommandsViewModel
{
public CommandsViewModel(GlobalEvents globalEvents)
{
this.CallView1Command = new DelegateCommand(globalEvents.RaiseView1Event);
}
public DelegateCommand CallView1Command { get; set; }
}
/// <summary>
/// Model where views are changed and then displayed
/// </summary>
public class DisplayedViewModel : INotifyPropertyChanged
{
public DisplayedViewModel(GlobalEvents globalEvents)
{
globalEvents.View1Event += (s,e) =>
this.CurrentView = new TextBlock { Text = "Pressed the button 1 and now here is the view 1" };
}
private object currentView;
public object CurrentView
{
get { return currentView; }
set
{
currentView = value;
RaisePropertyChanged("CurrentView");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In this example you must change the DelegateCommand class for something different. Other code will work for everyone.
It sounds like you might be trying to do some sort of navigation. If that's true, check out the Silverlight navigation framework.
I just noticed that when changing bound properties in my ViewModel (MVVM) from a background worker thread I do not get any exceptions and the view is updated correctly. Does this mean I can safely rely on wpf databinding marshalling all changes in the ViewModel to the UI Thread? I think I have read somewhere that one should make sure (in the ViewModel) that INotifyPropertyChanged.PropertyChanged is fired on the UI thread. Has this changed in 3.5 or something?
Yes for scalars, no for collections. For collections, you'll need a specialized collection that marshals for you, or manually marshal to the UI thread yourself via the Dispatcher.
You may have read that INotifyCollectionChanged.CollectionChanged must fire on the UI thread, because it's simply not true of INotifyPropertyChanged.PropertyChanged. Below is a very simple example that proves WPF marshals property changes for you.
Window1.xaml.cs:
using System.ComponentModel;
using System.Threading;
using System.Windows;
namespace WpfApplication1
{
public partial class Window1 : Window
{
private CustomerViewModel _customerViewModel;
public Window1()
{
InitializeComponent();
_customerViewModel = new CustomerViewModel();
DataContext = _customerViewModel;
var thread = new Thread((ThreadStart)delegate
{
while (true)
{
Thread.Sleep(2000);
//look ma - no marshalling!
_customerViewModel.Name += "Appended";
_customerViewModel.Address.Line1 += "Appended";
}
});
thread.Start();
}
}
public abstract class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class CustomerViewModel : ViewModel
{
private string _name;
private AddressViewModel _address = new AddressViewModel();
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public AddressViewModel Address
{
get { return _address; }
}
}
public class AddressViewModel : ViewModel
{
private string _line1;
public string Line1
{
get { return _line1; }
set
{
if (_line1 != value)
{
_line1 = value;
OnPropertyChanged("Line1");
}
}
}
}
}
Window1.xaml:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<TextBox Text="{Binding Name}"/>
<TextBox Text="{Binding Address.Line1}"/>
</StackPanel>
</Window>
I believe that with 2.0 and previous incarnations of .NET you would have received an InvalidOperationException due to thread affinity when executing the aforementioned example (link posted by bitbonk is dated 2006).
Now, with 3.5, WPF does appear to marshal background thread property changes onto the dispatcher for you.
So, in short, depends which version of .NET you're targetting. Hopefully that clears up any confusion.
One of my fellow Lab49'ers blogged about it here in 2007:
http://blog.lab49.com/archives/1166