How to navigate the Content Presenter in the Child View Model? - c#

Goal
My actual goal is to navigate the ContentPresenter not by the main navigation, but via a button within the navigated page.
My current results
This is my Main navigation on the left hand side:
When clicked on either of the main navigation items, the ContentPresenter will load it's ViewModel.
Here is the Home tab
and the Some Other tab
Expected results
My expectation is to click on the button (See image below) from the loaded View Model, and navigate to the other view model...
But I am not sure how to implement such idea.
Code
Page View Model
public class PageViewModel
{
public string Title { get; set; }
public object Content { get; set; }
public List<PageViewModel> Children { get; set; }
}
Main View Model
public class MainViewModel
{
public List<PageViewModel> Navigation { get; set; }
public MainViewModel()
{
Navigation = new List<PageViewModel>
{
new PageViewModel
{
Title = "Home",
Content = new HomeViewModel()
},
new PageViewModel
{
Title = "Some Other Tab",
Content = new SomeOtherViewModel()
}
};
}
}
MainWindow.xaml
...
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<ListView ItemsSource="{Binding Navigation}"
x:Name="Nav">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type local:PageViewModel}">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<ContentPresenter Content="{Binding ElementName=Nav, Path=SelectedItem.Content}"
Grid.Column="1"/>
</Grid>
</Window>
Home View Model
public class HomeViewModel
{
public string SomeTitle { get; set; }
public HomeViewModel()
{
SomeTitle = "Hello Home ViewModel";
}
}
Some Other View Model
public class SomeOtherViewModel
{
public string SomeTitle { get; set; }
public SomeOtherViewModel()
{
SomeTitle = "Hello SomeOther View Model";
}
}
Question
What would be the correct implementation to navigate via the internal (child) view model?

You must implement INotifyPropertyChanged in the MainViewModel and add a property called SelectedItem to bind to the listview.
I put the code to do this below. The code works properly.
PageViewModel.cs
public class PageViewModel
{
public string Title { get; set; }
public object Content { get; set; }
}
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
public List<PageViewModel> Navigation { get; set; }
private PageViewModel selectedItem { get; set; }
public PageViewModel SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
public MainViewModel()
{
Navigation = new List<PageViewModel>
{
new PageViewModel
{
Title = "Home",
Content = new HomeViewModel(this),
},
new PageViewModel
{
Title = "Some Other Tab",
Content = new SomeOtherViewModel(),
}
};
SelectedItem = Navigation.FirstOrDefault();
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
HomeViewModel.cs
public class HomeViewModel
{
public string SomeTitle { get; set; }
public object View { get; set; }
MainViewModel mainViewModel;
public RelayCommand SomeOtherCommand { get; private set; }
public HomeViewModel(MainViewModel _mainViewModel)
{
SomeTitle = "Hello Home ViewModel";
View = new View1(this);
mainViewModel = _mainViewModel;
SomeOtherCommand = new RelayCommand(SomeOtherMethod);
}
private void SomeOtherMethod(object parameter)
{
mainViewModel.SelectedItem = mainViewModel.Navigation.Where(a => a.Title == "Some Other Tab").FirstOrDefault();
}
}
SomeOtherViewModel.cs
public class SomeOtherViewModel
{
public string SomeTitle { get; set; }
public object View { get; set; }
public SomeOtherViewModel()
{
SomeTitle = "Hello SomeOther View Model";
View = new View2();
}
}
RelayCommand.cs
public class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new NullReferenceException("execute");
}
else
{
_execute = execute;
_canExecute = canExecute;
}
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
public RelayCommand(Action<object> execute) : this(execute, null)
{
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute.Invoke(parameter);
}
}
MainWindow.xaml
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<ListView ItemsSource="{Binding Navigation}" x:Name="Nav" SelectedItem="{Binding Path=SelectedItem,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type local:PageViewModel}">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<ContentPresenter Content="{Binding ElementName=Nav, Path=SelectedItem.Content.View}" Grid.Column="1" />
</Grid>
View1.xaml
<Grid>
<Button x:Name="btnGet" Content="get" Height="40" Command="{Binding SomeOtherCommand}"></Button>
</Grid>
View2.xaml
<Grid>
<Button Content="test"></Button>
</Grid>
View1.cs
public partial class View1 : UserControl
{
public View1(HomeViewModel homeViewModel)
{
InitializeComponent();
DataContext = homeViewModel;
}
}

Related

Why set Properties in new Window opened arent't shown?

Im learning MVVM at the moment and made a Login Window which opens first.
After Logging in, my MainWindow opens. The MainWindows Title is set via Property in MainWindowViewModel but doesn't shown (its empty) now when I open this window via LoginWindow instead of making it the StartUp Window.
This is the Code how I open MainWindow from Login.
LoginViewModel.cs
if (r)
{
CurrentUser.Username = Username;
Messenger.Default.Send(new NotificationMessage("CloseWindow"));
}
LoginView.xaml.cs
public Login()
{
InitializeComponent();
Messenger.Default.Register<NotificationMessage>(this, (message) =>
{
switch (message.Notification)
{
case "CloseWindow":
Messenger.Default.Send(new NotificationMessage("NewCourse"));
var MainWindow = new MainWindow();
MainWindow.Show();
this.Close();
break;
}
});
}
MainViewModel.cs
public MainViewModel()
{
if (IsInDesignMode)
{
WindowTitle = "Controlcenter (Designmode)";
CurrentUserLoggedIn = "Logged in as: " + CurrentUser.Username;
CurrentVersion = "Version: " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
}
else
{
WindowTitle = "Controlcenter";
CurrentUserLoggedIn = "Logged in as: " + CurrentUser.Username;
CurrentVersion = "Version: " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
}
}
public string WindowTitle { get; private set; }
public string CurrentUserLoggedIn { get; private set; }
public string CurrentVersion { get; private set; }
I dont know why but I think MainViewModel() isn't called.
Im using MVVMLight and PropertyChanged.Fody.
So my ViewModelLocator looks like this
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<DataErrorInfoViewModel>();
SimpleIoc.Default.Register<LoginViewModel>();
}
public MainViewModel Main => ServiceLocator.Current.GetInstance<MainViewModel>();
public LoginViewModel Login => ServiceLocator.Current.GetInstance<LoginViewModel>();
public DataErrorInfoViewModel DataErrorInfo => ServiceLocator.Current.GetInstance<DataErrorInfoViewModel>();
Is there something wrong in ViewModelLocator?
Edit:
MainWindow.xaml
<Window x:Class="Ui.Desktop.MainWindow"
[...]
xmlns:logic="clr-namespace:Logic.Ui;assembly=ControlcenterMVVM.Logic.Ui"
Title="{Binding WindowTitle, Mode=OneWay}"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Window.Resources>
<DataTemplate x:Name="firmcustomerViewTemplate" DataType="{x:Type logic:FirmcustomerViewModel}">
<local:Firmcustomer DataContext="{Binding}" />
</DataTemplate>
<DataTemplate x:Name="privatecustomerViewTemplate" DataType="{x:Type logic:PrivatecustomerViewModel}">
<local:Privatecustomer DataContext="{Binding}" />
</DataTemplate>
</Window.Resources>
<Grid>
[...]
<Label Content="{Binding CurrentUser}" FontWeight="Normal" FontSize="13" />
<Label Content="{Binding CurrentVersion}" />
</StackPanel>
<ContentControl Grid.Row="0" Grid.Column="1" Grid.RowSpan="3" Content="{Binding}" />
</Grid>
And MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new FirmcustomerViewModel();
}
private void firmcustomer_Click(object sender, RoutedEventArgs e)
{
DataContext = new FirmcustomerViewModel();
}
private void privatecustomer_Click(object sender, RoutedEventArgs e)
{
DataContext = new PrivatecustomerViewModel();
}
}
So what does PropertyChanged.Fody for me now?
In MainWindowViewModel i just add the Properties as follows:
public string WindowTitle { get; private set; }
public string CurrentUserLoggedIn { get; private set; }
public string CurrentVersion { get; private set; }
No I compile the Project and use dotpeek to decompile the project and see, how it looks like now
public string WindowTitle
{
get
{
return this.\u003CWindowTitle\u003Ek__BackingField;
}
private set
{
if (string.Equals(this.\u003CWindowTitle\u003Ek__BackingField, value, StringComparison.Ordinal))
return;
this.\u003CWindowTitle\u003Ek__BackingField = value;
this.RaisePropertyChanged(nameof (WindowTitle));
}
}
public string CurrentUserLoggedIn
{
get
{
return this.\u003CCurrentUserLoggedIn\u003Ek__BackingField;
}
private set
{
if (string.Equals(this.\u003CCurrentUserLoggedIn\u003Ek__BackingField, value, StringComparison.Ordinal))
return;
this.\u003CCurrentUserLoggedIn\u003Ek__BackingField = value;
this.RaisePropertyChanged(nameof (CurrentUserLoggedIn));
}
}
public string CurrentVersion
{
get
{
return this.\u003CCurrentVersion\u003Ek__BackingField;
}
private set
{
if (string.Equals(this.\u003CCurrentVersion\u003Ek__BackingField, value, StringComparison.Ordinal))
return;
this.\u003CCurrentVersion\u003Ek__BackingField = value;
this.RaisePropertyChanged(nameof (CurrentVersion));
}
}
So RaisePropertyChaned is there.

How to bind button to Listbox item

ViewModel
public class MainWindowViewModel:BindableBase
{
public IRelayCommand MyCommand { get; protected set; }
private void CreateCommand()
{
this.MyCommand = new RelayCommand(MyCommandExecuted, CanExecuteMyCommand);
}
private void MyCommandExecuted(object obj)
{
MessageBox.Show("Command Executed");
}
private bool CanExecuteMyCommand(object obj)
{
return true; // The value is based on Selected Item
}
}
XAML
<ListBox
x:Name="myListBox"
ItemsSource="{Binding Path=MyClass}"
<ListBox.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Path=HeaderName}" IsExpanded="True">
<StackPanel>
<DataGrid
x:Name="dataGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding Path=RowVal}" SelectedItem="{Binding CurrentItem}"/>
</StackPanel>
</Expander>
</DataTemplate>
</ListBox.ItemTemplate>
<Button Content="Select"
Command="{Binding Path=MyCommand }"
CommandParameter="{Binding ElementName=myListBox,Path=SelectedItem}"/>
DataClass
public class DataClass
{
public string HeaderName { get; set; }
public object RowVal { get; set; }
public ObservableCollection<DataGridColumn> ColumnCollection { get; set;}
private object currentItem;
public object CurrentItem
{
get
{
return currentItem;
}
set
{
currentItem = value;
}
}
}
How can I bind my button to Listbox item which is CurrentItem in DataClass ?
I created a complete example to show how I would do it. You would have to bind the parent element to SelectedItem as well, to keep track of when that item changes. Since the SelectedItem is public in your child class as well you can access that when your command triggers in your main view model.
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ListView ItemsSource="{Binding Parents}" SelectedItem="{Binding SelectedParent}">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type local:Parent}">
<DataGrid ItemsSource="{Binding Children}" SelectedItem="{Binding SelectedChild}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Grid.Row="1" Width="70" Content="Click me" Height="25" Command="{Binding MyCommand}" />
i.e. in DoWork, you can get the child from the parent via its public property.
public sealed class WindowViewModel : INotifyPropertyChanged
{
private readonly ObservableCollection<Parent> parents;
private readonly ICommand myCommand;
private Parent selectedParent;
public WindowViewModel()
{
parents = new ObservableCollection<Parent>
{
new Parent{ Name = "P1"},
new Parent{ Name = "P2"}
};
myCommand = new DelegateCommand(DoWork);
}
private void DoWork()
{
var selectedChild = SelectedParent == null ? null : SelectedParent.SelectedChild;
}
public Parent SelectedParent
{
get { return selectedParent; }
set
{
if (selectedParent == value)
return;
selectedParent = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Parent> Parents
{
get { return parents; }
}
public ICommand MyCommand
{
get { return myCommand; }
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
With the basic setup of Data models
public class Parent : INotifyPropertyChanged
{
private ObservableCollection<Child> children;
private Child m_SelectedChild;
public Parent()
{
children = new ObservableCollection<Child>
{
new Child {Name = "C1"},
new Child {Name = "C2"}
};
}
public string Name { get; set; }
public ObservableCollection<Child> Children
{
get { return children; }
}
public Child SelectedChild
{
get { return m_SelectedChild; }
set
{
if (m_SelectedChild == value)
return;
m_SelectedChild = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Child
{
public string Name { get; set; }
}
Another solution, if you are more interested of the Child item in your WindowViewModel is to change the relative source of where the binding should occur, in your DataGrid. i.e., the binding would look like this instead:
<DataGrid ItemsSource="{Binding Children}"
SelectedItem="{Binding DataContext.SelectedChild, RelativeSource={RelativeSource AncestorType={x:Type ListView}}}" />
and then move the Property from Parent to WindowViewModel. With that you would be able to trigger changes to your button command when the child element changes for any of the Parent elements.
I think you want to pass the CurrentItem to the MyCommand as CommandParameter right?
Then you only have to:
CommandParameter="{Binding CurrentItem, UpdateSourceTrigger=PropertyChanged}"
Try this :
CommandParameter="{Binding ElementName=myListBox,Path=SelectedItem.CurrentItem}"

I want to make binding in WPF TreeView

I'm trying to make control to add contacts Which has a TreeView. When I add contacts to the control displays nothing in the treeView. Here I show the code:
<TreeView x:Name="TvContactos" ItemsSource="{Binding Path=Groups}" HorizontalContentAlignment="Stretch" DockPanel.Dock="Left" ScrollViewer.CanContentScroll="True">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:ViewModelGroupContact}" ItemsSource="{Binding Children}">
<Grid Height="35">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding GroupName}" Style="{StaticResource BloStyle}" Grid.Column="0"/>
</Grid>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelContact}">
<Grid Height="38">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Height="32" Width="32" Source="Resources/User.jpg" Margin="3" Grid.Column="0"/>
<TextBlock Text="{Binding ContactName}" Style="{StaticResource BloStyle}" Grid.Column="1"/>
</Grid>
</DataTemplate>
</TreeView.Resources>
<TreeView.DataContext>
<local:ViewModelGroups/>
</TreeView.DataContext>
</TreeView.Resources>
In the code behind I have the following classes
public class ViewModelGroups : INotifyPropertyChanged
{
ObservableCollection<ViewModelGroupContact> _groups;
public ViewModelGroups()
{
Groups = new ObservableCollection<ViewModelGroupContact>();
}
public ObservableCollection<ViewModelGroupContact> Groups
{
get { return _groups; }
set
{
_groups = value;
OnPropertyChanged("Groups");
}
}
public void AddGroup(string groupName,RosterItem contact)
{
var newContact = new Contact {Name = contact.Name ?? contact.Jid.ToString(), RosterItem = contact};
var vmc = _groups.FirstOrDefault(item => item.GroupName == groupName);
if (vmc == null)
{
var contGroup = new ContactGroup { Name = groupName };
vmc = new ViewModelGroupContact(contGroup);
}
vmc.AddContactToGroup(newContact);
Dispatcher.CurrentDispatcher.BeginInvoke((new Action(() => Groups.Add(vmc))));
OnPropertyChanged("Groups");
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ViewModelGroupContact : TreeViewItemViewModel
{
private readonly ContactGroup _contactGroup;
public string GroupName { get; set; }
public ViewModelGroupContact(ContactGroup contactGroup)
: base(null, true)
{
_contactGroup = contactGroup;
GroupName = _contactGroup.Name;
}
protected override void LoadChildren()
{
foreach (Contact contact in _contactGroup.GetContacts())
base.Children.Add(new ViewModelContact(contact, this));
}
public void AddContactToGroup(Contact contact)
{
if (!_contactGroup.GetContacts().Contains(contact))
_contactGroup.AddContactToGroup(contact);
}
}
public class ViewModelContact:TreeViewItemViewModel
{
private readonly Contact _contact;
public ViewModelContact(Contact contact, ViewModelGroupContact group)
: base(group, true)
{
_contact = contact;
}
public string ContactName
{
get { return _contact.Name; }
}
}
When added a contact to treeview nothing is displayed.No show TreeViewItemViewModel class which inherits from INotifyPropertyChanged for not doing longer the post. This class has a property called Childrens.
This is the control class that was missing
public partial class ContactControl : UserControl
{
#region Private
private ViewModelGroups _viewModel;
private const string MDefaultGroupName = "ungrouped";
#endregion
public ContactControl()
{
InitializeComponent();
Init();
}
public ViewModelGroups ViewModel
{
get { return _viewModel; }
}
public void Init()
{
_viewModel = new ViewModelGroups();
TvContactos.DataContext = _viewModel;
}
public void AddContact(RosterItem ritem)
{
string groupname;
if (ritem.GetGroups().Count > 0)
{
var g = (Group)ritem.GetGroups().Item(0);
groupname = g.Name;
}
else
{
groupname = MDefaultGroupName;
}
_viewModel.AddGroup(groupname, ritem);
}
}

WPF DataGrids for hierarchical information

I have an application that contains an ObservableCollection<Foo>, and Foo in turn contains an ObservableCollection<Bar>. I would like a pair of datagrids, one showing the collection of Foo objects in the application and the other showing the collection of Bar objects in the Foo that's currently selected in the first datagrid (and I want to be able to add, update and delete entries in both datagrids).
So far I've got the following XAML:
<Window x:Class="Test.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">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<DataGrid Grid.Column="0" ItemsSource="{Binding Foos}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Foo Name" Binding="{Binding Name}" Width="Auto" IsReadOnly="False" />
</DataGrid.Columns>
</DataGrid>
<GridSplitter HorizontalAlignment="Right" VerticalAlignment="Stretch" Grid.Column="1" ResizeBehavior="PreviousAndNext" Width="5" Background="Gray" />
<DataGrid Grid.Column="2">
<DataGrid.Columns>
<DataGridTextColumn Header="Bar Name" Width="Auto" IsReadOnly="False"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
And the following code:
using System;
using System.Windows;
using System.Collections.ObjectModel;
namespace Test
{
public class Foo
{
static int _nextId;
public int Id { get; private set; }
public String Name { get; set; }
public ObservableCollection<Bar> Bars { get; set; }
public Foo()
{
Id = _nextId++;
Name = String.Empty;
Bars = new ObservableCollection<Bar>();
}
}
public class Bar
{
static int _nextId;
public int Id { get; private set; }
public String Name { get; set; }
public Bar()
{
Id = _nextId++;
Name = String.Empty;
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<Foo> Foos { get; set; }
public MainWindow()
{
Foos = new ObservableCollection<Foo>();
Foo newFoo;
for (int index = 0; index < 5; ++index)
{
newFoo = new Foo();
newFoo.Name = String.Format("Foo {0}", index);
Foos.Add(newFoo);
}
InitializeComponent();
DataContext = this;
}
}
}
Obviously I've not bound the 2nd DataGrid yet, because I've not got the faintest idea how to do it! All the examples I can find assume I'm binding DataTables, not custom objects, and bind to a relation on the DataTables. I don't really understand binding all that well yet. Can anybody tell me how to bind the 2nd table?
(And yes, if you've seen my other recent questions, I am giving WPF another shot after not getting on well with it in the early days).
Thanks in advance.
Hi If you want editable grids first of all you will have to implement INotifyPropertyChanged like
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel
{
public ViewModel()
{
Foos = new ObservableCollection<Foo>();
}
public ObservableCollection<Foo> Foos { get; set; }
}
public class Foo : INotifyPropertyChanged
{
static int _nextId;
public int Id { get; private set; }
public ObservableCollection<Bar> Bars { get; set; }
public Foo()
{
Id = _nextId++;
Name = String.Empty;
Bars = new ObservableCollection<Bar>();
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
Notify("Name");
}
}
private void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class Bar : INotifyPropertyChanged
{
static int _nextId;
public int Id { get; private set; }
public Bar()
{
Id = _nextId++;
Name = String.Empty;
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
Notify("Name");
}
}
private void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
In xaml Binding for first Grid is corrrect and for second grid you can set ItemsSource as the the selectedItem of First grid using ElementName
<DataGrid Grid.Column="2" ItemsSource="{Binding ElementName=gridTop, Path=SelectedItem.Bars}">
<DataGrid.Columns>
<DataGridTextColumn Header="Bar Name" Binding="{Binding Name}" Width="Auto" IsReadOnly="False"/>
</DataGrid.Columns>
</DataGrid>
Use Binding to Element
Name the top grid gridTop
DataContext="{Binding ElementName=gridTop, Path=SelectedItem.Bars}"

Passing custom objects to UserControl via XAML binding

What I'm trying to do is create a UserControl to which I can pass an Address object. It seems that when I pass Address="{Binding Path=Person.Address}" to the UserControl, the embedded TextBox is binding to Text="{Binding Path=Person.Address}" instead of Text="{Binding Path=Address.Summary}"
Am I going about this all wrong?
Here's a link to the project if you want to play with it: http://dl.dropbox.com/u/4220513/WpfApplication2.zip
Domain objects:
namespace WpfApplication2
{
public class Person
{
public String Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public String Street { get; set; }
public String City { get; set; }
public String Summary { get { return String.Format("{0}, {1}", Street, City); } }
}
}
MainWindow:
namespace WpfApplication2
{
public partial class MainWindow : Window
{
private readonly ViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new ViewModel();
DataContext = vm;
vm.Person = new Person()
{
Name = "Bob",
Address = new Address()
{
Street = "123 Main Street",
City = "Toronto",
},
};
}
}
public class ViewModel : INotifyPropertyChanged
{
private Person person;
public Person Person { get { return person; } set { person = value; NotifyPropertyChanged("Person"); } }
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBlock Text="Name:" />
<TextBlock Text="{Binding Path=Person.Name}" />
<TextBlock Text="Address:" />
<local:AddressView Address="{Binding Path=Person.Address}" />
</StackPanel>
</Window>
UserControl:
namespace WpfApplication2
{
public partial class AddressView : UserControl
{
public AddressView()
{
InitializeComponent();
DataContext = this;
}
public Address Address
{
get { return (Address)GetValue(AddressProperty); }
set { SetValue(AddressProperty, value); }
}
public static readonly DependencyProperty AddressProperty =
DependencyProperty.Register("Address", typeof(Address), typeof(AddressView));
}
}
<UserControl x:Class="WpfApplication2.AddressView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBox Text="{Binding Path=Address.Summary}" IsReadOnly="True" />
</UserControl>
Error:
System.Windows.Data Error: 40 : BindingExpression path error: 'Person' property not found on 'object' ''AddressView' (Name='')'. BindingExpression:Path=Person.Address; DataItem='AddressView' (Name=''); target element is 'AddressView' (Name=''); target property is 'Address' (type 'Address')
In MainWindow.xaml :
<local:AddressView DataContext="{Binding Path=Person.Address}" />
and then in AddressView.xaml
<TextBox Text="{Binding Path=Summary, Mode=OneWay}" IsReadOnly="True" />
This displays the summary for me.

Categories