I have MainWindow (simplified for clarity):
<Window>
<!-- (...) -->
<Window.DataContext>
<vm:MainWindowViewModel />
</Window.DataContext>
<!-- (...) -->
<CheckBox IsChecked="{Binding ShowAdvanced}" Content="Advanced view" />
<uc:MyUserControl DataContext={Binding MyUserControlViewModel} />
</Window>
MainWindowViewModel:
public partial class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
MyUserControlVM = new MyUserControlViewModel();
}
private bool _showAdvanced;
public bool ShowAdvanced
{
get => _showAdvanced;
set { _showAdvanced = value; NotifyPropertyChanged(); }
}
private MyUserControlViewModel _myUserControlVM;
public MyUserControlViewModel MyUserControlVM
{
get => _myUserControlVM;
set { _myUserControlVM= value; NotifyPropertyChanged(); }
}
}
In my UserControl I have some controls supposed to be hidden when "Show advanced" checkbox is not checked.
<GroupBox Header="Some advanced stuff"
Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext.(vm:MainWindowViewModel.ShowAdvanced), Converter={StaticResource BoolToVis}}">
<!-- (...) -->
</GroupBox>
This actually works, but I don't like this because UserControl relies on MainWindow.
How can I connect these viewmodels correctly without DependencyProperty?
I have tried to add this to MyUserControlViewModel:
public MyUserControlViewModel(MainWindowViewModel parent)
{
Parent = parent;
}
private MainWindowViewModel _parent;
public MainWindowViewModel Parent
{
get { return _parent; }
set { _parent = value; NotifyPropertyChanged(); }
}
and bind visibility on one of MyUserControl controls like this:
Visibility="{Binding Parent.ShowAdvanced}"
but this is not working (MyUserControl is not getting notified?).
Add the ShowAdvanced property to the control VM and assign the value to each control VM whenever a new value is assigned to the MainViewModel ShowAdvanced property.
public class MainViewModel : Base.ViewModelBase
{
private bool _showAdvanced;
public MainViewModel()
{
MyUserControl1 = new MyUserControlViewModel { Message = "Control 1" };
MyUserControl2 = new MyUserControlViewModel { Message = "Control 2" };
MyUserControl3 = new MyUserControlViewModel { Message = "Control 3" };
}
public bool ShowAdvanced
{
get => _showAdvanced;
set
{
this.RaiseAndSetIfChanged( ref _showAdvanced, value );
MyUserControl1.ShowAdvanced = value;
MyUserControl2.ShowAdvanced = value;
MyUserControl3.ShowAdvanced = value;
}
}
public MyUserControlViewModel MyUserControl1 { get; }
public MyUserControlViewModel MyUserControl2 { get; }
public MyUserControlViewModel MyUserControl3 { get; }
}
public class MyUserControlViewModel : Base.ViewModelBase
{
private bool _showAdvanced;
private string _message;
public bool ShowAdvanced { get => _showAdvanced; set => this.RaiseAndSetIfChanged( ref _showAdvanced, value ); }
public string Message { get => _message; set => this.RaiseAndSetIfChanged( ref _message, value ); }
}
Related
Avalonia.UI ComboBox doesn't have SelectedValuePath & DisplayMemberPath and I'm not skilled enough to implement them.
I have experimented with a TemplatedControl as a work-around because I'm using a TemplatedControl anyway and I don't want to have extra Properties on the ViewModel to handle it.
What I'm trying to archive is to show the items in the ComboBox by DisplayMember but loading/saving by Id. In this test case by Project.ManagerId. It's working but it would be nice not to have to hard-code the Type in that TemplatedControl code behind, like I do right now with "LookupItem". It should be possible to get around that by setting x:DataType="viewModels:LookupItem" or something.
Any ideas?
I have removed most not relevant code.
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:ChangeTrackingHeaderControls.Controls">
<Design.PreviewWith>
<controls:ChangeTrackingHeaderComboBox />
</Design.PreviewWith>
<Style Selector="controls|ChangeTrackingHeaderComboBox">
<!-- Set Defaults -->
<Setter Property="Template">
<ControlTemplate>
<ComboBox Items="{TemplateBinding Items, Mode=OneWay}"
SelectedItem="{TemplateBinding SelectedItem, Mode=TwoWay}"
SelectedIndex="{TemplateBinding SelectedIndex, Mode=TwoWay}"
ItemTemplate="{TemplateBinding ItemTemplate}"
HorizontalAlignment="Stretch" />
</ControlTemplate>
</Setter>
</Style>
</Styles>
public class ChangeTrackingHeaderComboBox : TemplatedControl
{
private object? _selectedItem;
private int? _selectedValue;
public static readonly DirectProperty<ChangeTrackingHeaderComboBox, object?> SelectedItemProperty =
AvaloniaProperty.RegisterDirect<ChangeTrackingHeaderComboBox, object?>(nameof(SelectedItem),
o => o.SelectedItem,
(o, v) => o.SelectedItem = v,
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true);
public static readonly DirectProperty<ChangeTrackingHeaderComboBox, int?> SelectedValueProperty =
AvaloniaProperty.RegisterDirect<ChangeTrackingHeaderComboBox, int?>(nameof(SelectedValue),
o => o.SelectedValue,
(o, v) => o.SelectedValue = v,
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true);
public static readonly StyledProperty<IEnumerable> ItemsProperty = AvaloniaProperty
.Register<ChangeTrackingHeaderComboBox, IEnumerable>(nameof(Items));
public static readonly StyledProperty<int> SelectedIndexProperty = AvaloniaProperty
.Register<ChangeTrackingHeaderComboBox, int>(nameof(SelectedIndex));
public static readonly StyledProperty<IDataTemplate> ItemTemplateProperty = AvaloniaProperty
.Register<ChangeTrackingHeaderComboBox, IDataTemplate>(nameof(ItemTemplate));
public object? SelectedItem
{
get => _selectedItem;
set
{
SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
OnSelectedItemChanged();
}
}
public int? SelectedValue
{
get => _selectedValue;
set
{
SetAndRaise(SelectedValueProperty, ref _selectedValue, value);
OnSelectedValueChanged();
}
}
public IEnumerable Items
{
get => GetValue(ItemsProperty);
set => SetValue(ItemsProperty, value);
}
public int SelectedIndex
{
get => GetValue(SelectedIndexProperty);
set => SetValue(SelectedIndexProperty, value);
}
public IDataTemplate ItemTemplate
{
get => GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error)
{
base.UpdateDataValidation(property, state, error);
if (property == SelectedItemProperty)
{
DataValidationErrors.SetError(this, error);
}
if (property == SelectedValueProperty)
{
DataValidationErrors.SetError(this, error);
}
}
private void OnSelectedItemChanged()
{
// LookupItem should not be hard coded
if (SelectedItem is LookupItem selectedItem)
{
if (SelectedValue == selectedItem.Id) return;
if (Items is IEnumerable<LookupItem> items)
{
var selectedItemDisplayMember = selectedItem.DisplayMember;
SelectedValue = items.SingleOrDefault(x => x.DisplayMember == selectedItemDisplayMember).Id;
}
}
}
private void OnSelectedValueChanged()
{
// LookupItem should not be hard coded
if (Items is IEnumerable<LookupItem> items)
{
if (SelectedItem == null)
{
SelectedItem = items.SingleOrDefault(x => x.Id == SelectedValue);
return;
}
if (SelectedItem is LookupItem selectedItem)
{
if (selectedItem.Id == SelectedValue) return;
SelectedItem = items.SingleOrDefault(x => x.Id == SelectedValue);
}
}
}
}
// App.axaml
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:ChangeTrackingHeaderControls.Controls"
xmlns:viewModels="clr-namespace:ChangeTrackingHeaderControls.ViewModels"
x:Class="ChangeTrackingHeaderControls.App">
<Application.Resources>
<DataTemplate x:Key="LookupItemItemTemplate" x:DataType="viewModels:LookupItem">
<ComboBoxItem Content="{CompiledBinding DisplayMember}"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center" />
</DataTemplate>
</Application.Resources>
<Application.Styles>
<FluentTheme Mode="Light" />
<StyleInclude Source="/Controls/ChangeTrackingHeaderComboBox.axaml" />
</Application.Styles>
</Application>`
// MainWindow
<controls:ChangeTrackingHeaderComboBox Items="{CompiledBinding Managers}"
SelectedValue="{CompiledBinding Project.ManagerId}"
ItemTemplate="{StaticResource LookupItemItemTemplate}" />
// ViewModel
public sealed class MainViewModel : ViewModelBase
{
private Project _project;
public MainViewModel()
{
Managers = new ObservableCollection<LookupItem>();
// Simulating loading Managers from database
for (var i = 1; i < 12; i++)
{
Managers.Add(new LookupItem
{
Id = i,
DisplayMember = $"Manager {i}"
});
}
// One single Project loaded from database
Project = new Project
{
Id = 1,
Name = "Project 1",
ManagerId = Managers.LastOrDefault()?.Id
};
}
public ObservableCollection<LookupItem> Managers { get; }
public Project Project
{
get => _project;
private set
{
if (value == _project) return;
_project = value;
OnPropertyChanged();
}
}
}
// Models
public sealed class Project
{
public int Id { get; set; }
public string Name { get; set; }
public int? ManagerId { get; set; }
}
public sealed class LookupItem
{
public int Id { get; init; }
public string DisplayMember { get; init; }
}```
I have tried to use x:DataType="viewModels:LookupItem" and also implemented StyledProperty<Type> but no succcess.
On the ViewModel instances bound to the ComboBox's Items property, override ToString() and return the value you want displayed in the open drop down.
So i have WPF application with main windoes and 2 UserControls:
HomeView.xaml
OptionsView.xaml
View Model
public class ApplicationViewModel : INotifyPropertyChanged
{
#region Fields
private ICommand changePageCommand;
private ICommand addFilesCommand;
private IPageViewModel _currentPageViewModel;
private List<IPageViewModel> _pageViewModels;
#endregion
public ApplicationViewModel()
{
// Add available pages
PageViewModels.Add(new HomeViewModel() { IsSelected = true });
PageViewModels.Add(new OptionsViewModel() { IsSelected = false });
// Set starting page
CurrentPageViewModel = PageViewModels[0];
}
#region Properties / Commands
public ICommand ChangePageCommand
{
get
{
if (changePageCommand == null)
{
changePageCommand = new RelayCommand(
p => ChangeViewModel((IPageViewModel)p),
p => p is IPageViewModel);
}
return changePageCommand;
}
}
public List<IPageViewModel> PageViewModels
{
get
{
if (_pageViewModels == null)
_pageViewModels = new List<IPageViewModel>();
return _pageViewModels;
}
}
public IPageViewModel CurrentPageViewModel
{
get
{
return _currentPageViewModel;
}
set
{
if (_currentPageViewModel != value)
{
_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");
}
}
}
#endregion
#region Methods
private void ChangeViewModel(IPageViewModel viewModel)
{
if (!PageViewModels.Contains(viewModel))
PageViewModels.Add(viewModel);
CurrentPageViewModel = PageViewModels.FirstOrDefault(vm => vm == viewModel);
}
#endregion
}
Whan application start
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow app = new MainWindow();
ApplicationViewModel context = new ApplicationViewModel();
app.DataContext = context;
app.Show();
}
}
Windows respurces
<Window.Resources>
<DataTemplate DataType="{x:Type home:HomeViewModel}">
<home:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type options:OptionsViewModel}">
<options:OptionsView />
</DataTemplate>
</Window.Resources>
And inside HomeView.xaml i have simple button:
<Button Command="{Binding DataContext.AddFilesCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding}"/>
And i want to add simple Click command, something.
So i try to add this ICommand:
public ICommand AddFilesCommand
{
}
Any suggestions how to add this kind on command that will execute after each Button Click ?
This can be done a lot easier. I would create a class to implement commands easily:
using System;
using System.Windows.Input;
namespace YourNameSpace
{
public class RelayCommand : ICommand
{
private Action Action;
public Command(Action _action)
{
Action = _action;
}
public event EventHandler CanExecuteChanged = (sender, e) => { };
public bool CanExecute(object parameter) => true;
public void Execute(object parameter)
{
Action();
}
}
}
Then create the command (you don't need private ICommand for this):
public ICommand AddFileCommand { get; set; }
And use it like this (in the constructor):
AddFileCommand = new RelayCommand(()=>
{
MethodToExecute();
});
XAML:
<Button Command="{Binding AddFileCommand}"/>
This way your code will be easier to see trough.
Am working with WPF MVVM,
I have a main window and few usercontrols.based on the button click am showing the view and viewmodels. When i click button in main window i can navigate to different view. because navigation command is in mainwindow viewmodel only. But when am in different view(user control) . How can i navigate from viewmodel.
MainwindowViewmodel
public class MainWindowViewModel : BindableBase, INotifyPropertyChanged
{
private StudyViewModel _studyViewModel = new StudyViewModel();
private ImageCaptureViewModel _imageCaptureViewModel = new ImageCaptureViewModel();
private RegisterViewModel _registerViewModel = new RegisterViewModel();
private BindableBase _CurrentViewModel;
public BindableBase CurrentViewModel
{
get { return _CurrentViewModel; }
set { SetProperty(ref _CurrentViewModel, value); OnPropertyChanged("_CurrentViewModel"); }
}
public MainWindowViewModel()
{
NavCommand = new RelayCommand<string>(onNav);
onNav("study");
}
//This is the command am using in xaml to redirect view.
public RelayCommand<string> NavCommand { get; private set; }
private void onNav(string destination)
{
switch (destination)
{
case "study":
CurrentViewModel = _studyViewModel;
break;
case "capture":
CurrentViewModel = _imageCaptureViewModel;
break;
case "register":
CurrentViewModel = _registerViewModel;
break;
default:
CurrentViewModel = _studyViewModel;
break;
}
}
}
RegisterViewModel
public void RegisterPatient(string action)
{
if (action == "addnew")
{
}
else
{
if (StartImaging)
{
//Have to redirect to other screen.
}
}
var a = PatientToAdd;
MessageBox.Show("triggered");
}
When am adding command in mouse event it is redirecting .But i dont no how to redirect from viewmodel
RegisterView.xaml
<DataGrid.InputBindings>
<MouseBinding Command="{Binding DataContext.NavCommand,RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
MouseAction="LeftDoubleClick"
CommandParameter="historyimage"/>
</DataGrid.InputBindings>
Your question is not clear enough, so this is based on what I think you need.
MainWindow:
<Window x:Class="MyApp.MainWindow" ...>
...
<ContentControl Content={Binding MyCurrentViewModel}>
<ContentControl.Resources>
<DataTemplate DataType={x:Type vm:UserListViewModel}>
<view:UserListView />
</DataTemplate>
<DataTemplate DataType={x:Type vm:AnotherViewModel}>
<view:AnotherView />
</DataTemplate>
...
</ContentControl.Resources>
</ContentControl>
</Window>
MainViewModel:
private ViewModelBase _myCurrentViewModel;
public ViewModelBase MyCurrentViewModel
{
get { return _myCurrentViewModel; }
set
{
if (value != _myCurrentViewModel)
{
_myCurrentViewModel = value;
RaisePropertyChanged("MyCurrentViewModel");
}
}
}
UserListViewModel:
public class UserListViewModel : ViewModelBase
{
private MainViewModel MainVM;
public UserListViewModel(MainViewModel mainVM)
{
this.MainVM = mainVM;
}
private ICommand _myCommand;
public ICommand MyCommand
{
get
{
if (_myCommand = null)
_myCommand = new RelayCommand(MyExecuteDelegate, true);
return _myCommand;
}
}
private void MyExecuteDelegate(object parameter)
{
// My other logic
if (this.MainVM != null)
this.MainVM.MyCurrentViewModel = new AnotherViewModel();
}
}
I have a Model
public class Irritant : BindableBase
{
private short _id;
private string _name;
private string _description;
public short Id
{
get { return _id; }
set { SetProperty(ref _id, value); }
}
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
public string Description
{
get { return _description; }
set { SetProperty(ref _description, value); }
}
public Irritant()
{
Id = 0;
Name = "";
Description = "";
}
}
Then my ViewModel with two versions
public class IrritantViewModel : BindableBase
{
private IrritantDb db = new IrritantDb();
//Version 1 - The Model's property is coded in IrritantViewModel
//private short _id;
//private string _name = "Alen";
//private string _description;
//public short Id
//{
// get { return _id; }
// set { SetProperty(ref _id, value); }
//}
//public string Name
//{
// get { return _name; }
// set { SetProperty(ref _name, value); }
//}
//public string Description
//{
// get { return _description; }
// set { SetProperty(ref _description, value); }
//}
//Version2 - I use the Irritant Model as property of IrritantViewModel
private DateTime? _lastUpdated;
private Irritant _entity;
public Irritant Entity
{
get { return _entity; }
set { SetProperty(ref _entity, value); }
}
public DateTime? LastUpdated
{
get { return _lastUpdated; }
set { SetProperty(ref _lastUpdated, value); }
}
public DelegateCommand UpdateCommand { get; set; }
public IrritantViewModel()
{
Entity = new Irritant();
//Version1
//UpdateCommand = new DelegateCommand(EditCommand, CanExecute).ObservesProperty(() => Name);
//Version2
UpdateCommand = new DelegateCommand(EditCommand, CanExecute).ObservesProperty(() => Entity.Name);
}
private bool CanExecute()
{
//Version1
//switch (Name)
//{
// case null:
// return false;
// case "":
// return false;
//}
//Version2
switch (Entity.Name)
{
case null:
return false;
case "":
return false;
}
return true;
}
private void EditCommand()
{
LastUpdated = DateTime.UtcNow;
}
}
And this is my View
public partial class IrritantView : UserControl
{
public IrritantView()
{
InitializeComponent();
DataContext = new IrritantViewModel();
}
}
<Grid >
<ScrollViewer>
<StackPanel MinWidth="200">
<TextBlock Text="Irritant" />
<!--Version 1-->
<!--<TextBlock Text="Name" />
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="Description" />
<TextBox Text="{Binding Description, UpdateSourceTrigger=PropertyChanged}" />
-->
<!--Version 2-->
<TextBlock Text="Name" />
<TextBox Text="{Binding Entity.Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="Description" />
<TextBox Text="{Binding Entity.Description, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="Last Updated" />
<Label Content="{Binding LastUpdated, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Save"
Command="{Binding UpdateCommand}"
/>
</StackPanel>
</ScrollViewer>
</Grid>
The Version1 works fine, the Save Button disables when the TextBox that is bound to Name (TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}") is null or empty.
But with version 2, the Save Button doesn't disable. It only calls the method CanExecute during initialization, removing the text in the TextBox doesn't disable the Button. What did I do wrong?
DelegateCommand doesn't raise CanExecuteChanged event automatically, you have to raise that event manually by calling RaiseCanExecuteChanged when appropriate. Apart from using DelegateCommand, you can use RelayCommand which relays on CommandManager.RequerySuggested event which do the similar thing for you.
Change your Command definition returning ICommand:
public ICommand UpdateCommand { get; set; }
Initialize the command using below:
UpdateCommand = new AutoCanExecuteCommand(new DelegateCommand(EditCommand, CanExecute));
Use the following class as a wrapper:
public class AutoCanExecuteCommand : ICommand
{
public ICommand WrappedCommand { get; private set; }
public AutoCanExecuteCommand(ICommand wrappedCommand)
{
if (wrappedCommand == null)
{
throw new ArgumentNullException("wrappedCommand");
}
WrappedCommand = wrappedCommand;
}
public void Execute(object parameter)
{
WrappedCommand.Execute(parameter);
}
public bool CanExecute(object parameter)
{
return WrappedCommand.CanExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
I wouldn't recommend hooking into the CommandManager for a number of reasons. Besides memory leaks, it can introduce performance problems in your apps since you have no control over when, or how many times, the CommandManager will invoke the CanExecute (which happens on the UI thread). Instead, I would recommend using the INPC of your model object instead as shown in this answer:
ObservesProperty method isn't observing model's properties at Prism 6
I want to bind a TextBox in the window to a property contained within a class that is a variable of the viewmodel and ensure that INotifyPropertyChanged's PropertyChanged event propagates from the class to the parent.
Let me illustrate with an example:
(Window's DataContext is set to an instance of ViewModel)
public class ViewModel {
private OtherClass classInstance = new OtherClass();
public int Attribute {
get { return classInstance.Attribute; }
}
}
public class OtherClass : INotifyPropertyChanged {
private int _attribute;
public int Attribute {
get { return _attribute; }
set {
_attribute = value;
PropertyChanged("Attribute");
}
}
...
}
The problem in this example is that, when Attribute changes, the bound Textbox does not update the binding since I assume it's listening to the ViewModel's PropertyChanged event and not that of the instance of OtherClass.
Any ideas on how to remedy this situation? I was thinking about chaining OtherClass's INotifyPropertyChanged to that of its parent, but there has to be a better way.
Why not bind directly to the class property instead of using a proxy?
public class ViewModel {
private OtherClass classInstance = new OtherClass();
public OtherClass MyOtherClass {
get { return classInstance; }
}
}
Then in your binding you can simply reference the property via the SubClass
{Binding MyOtherClass.Attribute}
A drop dead simple example, but it works!
The Code Behind:
public partial class MainWindow : Window {
private readonly SomeClass _someClass = new SomeClass();
public MainWindow() {
InitializeComponent();
DataContext = _someClass;
}
}
public class SomeClass: INotifyPropertyChanged {
private readonly SomeSubClass _mySubClass = new SomeSubClass();
public SomeSubClass MySubClass {
get { return _mySubClass; }
}
private String _name;
public String Name {
get { return _name; }
set {
_name = value;
OnPropertyChanged("Name");
}
}
//Property Change Stuff
}
public class SomeSubClass : INotifyPropertyChanged {
private String _name;
public String Name {
get {
return _name;
}
set {
_name = value;
OnPropertyChanged("Name");
}
}
//Property Change Stuff
}
The XAML:
<Window x:Class="JWC.Examples.WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow">
<StackPanel>
<Label Content="Name" VerticalAlignment="Top" />
<TextBox Text="{Binding Name}" />
<Label Content="SubClass.Name" />
<TextBox Text="{Binding MySubClass.Name}" />
<Label Content="Bound to Name" />
<TextBlock Text="{Binding Name}" />
<Label Content="Bound to MySubClass.Name" />
<TextBlock Text="{Binding MySubClass.Name}" />
</StackPanel>
</Window>
You will need to do something like this:
public class ViewModel {
public ViewModel() {
classInstance = new OtherClass();
classInstance.PropertyChanged += HandleAttributeChanged;
}
private void HandleAttributeChanged(object Sender, NotifyPropertyChangedEventArgs args) {
PropertyChanged("Attribute");
}
}
I don't show it here, but you should also implement IDisposable and unsubscribe from the PropertyChanged event on your child, otherwise you will leak memory.
Alternatively you can expose the classInstance as a public property and set your binding to: ViewModel.classInstance. Note this needs to be a property and not the field itself.
I think the parent class should subscribe to the child propertyCahnged event..something like:
public class ViewModel
{
private OtherClass classInstance = new OtherClass();
public ViewModel()
{
classInstance.PropertyChanged += NotifyChildAttributeChanged;
}
public int Attribute
{
get { return classInstance.Attribute; }
}
}
NotifyChildAttributeChanged is basically a method that listens only to the "Attribute" property and fires a PropertyChanged notification of its own..
Of course our parent class must implement INotifyPropertyChanged as well as will all ViewModels (preferably) and your DataContext will detect the change.
To get around this you need to implement INotifyPropertyChanged on your view model as well. Just add the interface and the event and the rest will take care of itself - no need to chain the events / calls together.
Check out this for using reflection to get the property as well.
http://tsells.wordpress.com/2011/02/08/using-reflection-with-wpf-and-the-inotifypropertychanged-interface/