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}"
Related
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;
}
}
I have a TreeView, where I want to add an item with a right click. Therefore I use a command, to insert a new item.
The problem is now that adding/removing items won't be reflected in the UI and I don't know why.
The ObersavableCollections are hold in a property in my MainWindow
public static ObservableCollection<PlantObject> PlantObjects { get; set; }
The code works the way I want thus if I check the property and the item itself, it will be added but the TreeView will not reflect the changes. This can be shown by setting the ItemsSource of the TreeView null and then again to PlantObjects, which then displays the added item.
I'm pretty sure it the problem is that I add an item to the property "parents". But I don't know how to solve it.
You can see my Xaml and my Class below. NotifyPropertyChanged is implemented.
Thanks for any advice.
XAML
<TreeView Name="ContextTreeView" Margin="0,9.8,0.8,5" Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Source={x:Static local:MainWindow.PlantObjects} }">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:PlantObject}" ItemsSource="{Binding PlantObjects}" >
<StackPanel Orientation="Horizontal">
<StackPanel.ContextMenu>
<ContextMenu >
<MenuItem Header="Delete" Name="MenuItem_DeleteContextType" Command="{Binding DeleteContextTypeCommand}"/>
<MenuItem Header="Insert new object above" Name="ContextMenuItem_InsertAbovePlantObject" Command="{Binding InsertAboveCommand}"/>
</ContextMenu>
</StackPanel.ContextMenu>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
My Class
public class PlantObject : INotifyPropertyChanged
{
public PlantObject()
{
CreateInsertAboveCommand();
}
private bool _IsSelected;
private PlantObject parent;
private string name;
public byte[] ParentID { get; set; }
public byte[] ChildID { get; set; }
public byte[] ObjectID { get; set; }
private ObservableCollection<PlantObject> plantObjects = new ObservableCollection<PlantObject>();
public PlantObject Parent
{
get
{
return parent;
}
set
{
if (value != parent)
{
parent = value;
NotifyPropertyChanged();
}
}
}
public string Name
{
get
{
return name;
}
set
{
if (value != name)
{
name = value;
NotifyPropertyChanged();
}
}
}
public ObservableCollection<PlantObject> PlantObjects
{
get { return plantObjects; }
set
{
//ignore if values are equal
if (value == plantObjects) return;
plantObjects = value;
NotifyPropertyChanged();
}
}
public bool IsSelected
{
get { return _IsSelected; }
set
{
if (_IsSelected == value) return;
_IsSelected = value;
NotifyPropertyChanged();
}
}
public int? GetPosition(PlantObject plantObject)
{
int count = 0;
foreach (var childObject in this.plantObjects)
{
if (plantObject == childObject)
{
return count;
}
count++;
}
return null;
}
protected void Insert(int position, PlantObject plantObject)
{
Parent.PlantObjects.Insert(position, plantObject);
}
protected void Remove(PlantObject plantObject)
{
Parent.PlantObjects.RemoveAt(Parent.PlantObjects.IndexOf(plantObject));
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#region commands
#region InsertAbove Command
public ICommand InsertAboveCommand
{
get;
internal set;
}
private bool CanExecuteInsertAboveCommand()
{
return true;
}
private void CreateInsertAboveCommand()
{
InsertAboveCommand = new RelayCommand(InsertAbove);
}
public void InsertAbove(object obj)
{
//this.IsSelected = true;
if (this.parent == null)
{ //highest level
MainWindow.PlantObjects.Insert(MainWindow.PlantObjects.IndexOf(this), new PlantObject {IsSelected = true, Parent = this.Parent });
return;
}
int position = this.parent.GetPosition(this) ?? default(int);
PlantObject newPlantObejct = new PlantObject { Parent = this.Parent, IsSelected = true };
this.Insert(position, newPlantObejct);
}
#endregion
#endregion
}
I have this TreeView control:
<TreeView x:Name="treeView" HorizontalAlignment="Left" Margin="0" Width="300" ItemsSource="{Binding TvMview.FirstNodes}"
VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SecondNodes}">
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ThirdNodes}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding HeaderText}" Background="Red"/>
</StackPanel>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text=">>> " Background="Green"/>
<TextBlock Text="{Binding HeaderText}" Background="blue"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Binded to:
public class ModelView
{
public TreeViewModelView TvMview { get; set; } = new TreeViewModelView();
}
public class TreeViewModelView
{
public ObservableCollection<FirstNode> FirstNodes { get; set; } = new ObservableCollection<FirstNode>();
}
public class FirstNode : TreeItem
{
public ObservableCollection<SecondNode> SecondNodes { get; set; } = new ObservableCollection<SecondNode>();
public string HeaderText { get; set; }
}
public class SecondNode : TreeItem
{
public ObservableCollection<ThirdNode> ThirdNodes { get; set; } = new ObservableCollection<ThirdNode>();
public string HeaderText { get; set; }
}
public class ThirdNode
{
public string HeaderText { get; set; }
}
public class TreeItem
{
public bool IsExpanded { get; set; }
public bool IsSelected { get; set; }
}
And everything is working as expected. But... I want to implement seach:
foreach (var firstNode in Mview.TvMview.FirstNodes)
{
if (firstNode.HeaderText.Contains(textBox.Text))
{
firstNode.IsExpanded = true;
firstNode.IsSelected = true;
TreeViewItem tvItem = (TreeViewItem)treeView.ItemContainerGenerator.ContainerFromItem(firstNode);
tvItem?.Focus();
return;
}
}
The right node is expanded but tvItem is always null. I read somewhere sometime that ContainerFromItem doesn't likes virtualization...
Any idea?
Thanks!
Finally i solved this issue implementing a Focus() method in the TreeItem base class. So, base class look like this:
public class TreeItem : INotifyPropertyChanged
{
private bool _IsExpanded;
public bool IsExpanded
{
get { return _IsExpanded; }
set { _IsExpanded = value; OnPropertyChanged("IsExpanded"); }
}
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set { _IsSelected = value; OnPropertyChanged("IsSelected"); }
}
public void Focus(TreeView tView)
{
TreeViewItem tvItem = (TreeViewItem)tView.ItemContainerGenerator.ContainerFromItem(this);
tvItem?.Focus();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
And you can call it like this:
if (firstNode.HeaderText.Contains(textBox.Text))
{
firstNode.IsExpanded = true;
firstNode.IsSelected = true;
firstMatch.Focus();
}
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);
}
}
I have ViewModel(implemented INotifyPropertyChanged) in the background and class Category which has only one property of type string. My ComboBox SelectedItem is bind to an instance of a Category. When i change the value of instance, SelectedItem is not being updated and Combobox is not changed.
EDIT: code
Combobox:
<ComboBox x:Name="categoryComboBox" Grid.Column="1" Grid.Row="3" Grid.ColumnSpan="2"
Margin="10" ItemsSource="{Binding Categories}"
DisplayMemberPath="Name" SelectedValue="{Binding NodeCategory, Mode=TwoWay}"/>
Property:
private Category _NodeCategory;
public Category NodeCategory
{
get
{
return _NodeCategory;
}
set
{
_NodeCategory = value;
OnPropertyChanged("NodeCategory");
}
}
[Serializable]
public class Category : INotifyPropertyChanged
{
private string _Name;
[XmlAttribute("Name")]
public string Name
{
get
{
return _Name;
}
set
{
_Name = value;
OnPropertyChanged("Name");
}
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
[field:NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
}
and what I am trying is: when I set
NodeCategory = some_list_of_other_objects.Category;
to have that item selected in Combobox with appropriate DisplayMemberPath
The category you are setting in this line -
NodeCategory = some_list_of_other_objects.Category;
and one present in your Categories collection(ItemsSource="{Binding Categories}") should be referring to same object. If they are not then SelectedItem won't work.
Solution 1 -
You can also try to use SelectedValuePath like this -
<ComboBox x:Name="categoryComboBox"
ItemsSource="{Binding Categories}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding NodeCategory, Mode=TwoWay}" />
and in code you can do something like this -
private string _NodeCategory;
public string NodeCategory
{
get
{
return _NodeCategory;
}
set
{
_NodeCategory = value;
OnPropertyChanged("NodeCategory");
}
}
and set selected item like this -
NodeCategory = some_list_of_other_objects.Category.Name;
and use selected value like this -
Category selectedCategory =
some_list_of_other_objects.FirstOrDefault(cat=> cat.Name == NodeCategory);
or
Category selectedCategory =
Categories.FirstOrDefault(cat=> cat.Name == NodeCategory);
Solution 2 -
Another possible solution can be -
NodeCategory =
Categories.FirstOrDefault(cat=> cat.Name == some_list_of_other_objects.Category.Name);
this way your NodeCategory property will have the reference of an object in Categories collection and SelectedItem will work.
Your XAML needs a couple of modifications but I think the real problem is with the code you have posted which I don't think is telling the full story.
For starters, your combobox ItemSource is bound to a property called Categories but you do not show how this property is coded or how your NodeCategory property is initially synced with the item.
Try using the following code and you will see that the selected item is kept in sync as the user changes the value in the combobox.
XAML
<Window x:Class="WpfApplication1.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">
<StackPanel>
<ComboBox x:Name="categoryComboBox"
Grid.Column="1"
Grid.Row="3"
Grid.ColumnSpan="2"
Margin="10"
ItemsSource="{Binding Categories}"
DisplayMemberPath="Name"
SelectedItem="{Binding NodeCategory}" />
<Label Content="{Binding NodeCategory.Name}" />
</StackPanel>
Code-behind
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Category> _categories = new ObservableCollection<Category>
{
new Category { Name = "Squares"},
new Category { Name = "Triangles"},
new Category { Name = "Circles"},
};
public MainWindow()
{
InitializeComponent();
NodeCategory = _categories.First();
this.DataContext = this;
}
public IEnumerable<Category> Categories
{
get { return _categories; }
}
private Category _NodeCategory;
public Category NodeCategory
{
get
{
return _NodeCategory;
}
set
{
_NodeCategory = value;
OnPropertyChanged("NodeCategory");
}
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
[Serializable]
public class Category : INotifyPropertyChanged
{
private string _Name;
[XmlAttribute("Name")]
public string Name
{
get
{
return _Name;
}
set
{
_Name = value;
OnPropertyChanged("Name");
}
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
}
From my little example:
Note: This is setting just a string (or a category from another list), but the basics should be same here:
Basically this is done:
private void button1_Click(object sender, RoutedEventArgs e)
{
(this.DataContext as ComboBoxSampleViewModel).SelectCategory("Categorie 4");
}
Here is my XAML:
<Grid>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="76,59,0,0"
Name="comboBox1" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding List.Categories}"
DisplayMemberPath="Name"
SelectedValue="{Binding NodeCategory, Mode=TwoWay}" />
<Button Content="Button" Height="27" HorizontalAlignment="Left"
Margin="76,110,0,0" Name="button1" VerticalAlignment="Top"
Width="120" Click="button1_Click" />
</Grid>
and in the ViewModel of the Window
class ComboBoxSampleViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public CategoryList List { get; set; }
public ComboBoxSampleViewModel()
{
this.List = new CategoryList();
NodeCategory = List.Selected;
}
private ComboBoxSampleItemViewModel nodeCategory;
public ComboBoxSampleItemViewModel NodeCategory
{
get
{
return nodeCategory;
}
set
{
nodeCategory = value;
NotifyPropertyChanged("NodeCategory");
}
}
internal void SelectCategory(string p)
{
this.List.SelectByName(p);
this.NodeCategory = this.List.Selected;
}
}
With the help of this little class:
public class CategoryList
{
public ObservableCollection<ComboBoxSampleItemViewModel> Categories { get; set; }
public ComboBoxSampleItemViewModel Selected { get; set; }
public CategoryList()
{
Categories = new ObservableCollection<ComboBoxSampleItemViewModel>();
var cat1 = new ComboBoxSampleItemViewModel() { Name = "Categorie 1" };
var cat2 = new ComboBoxSampleItemViewModel() { Name = "Categorie 2" };
var cat3 = new ComboBoxSampleItemViewModel() { Name = "Categorie 3" };
var cat4 = new ComboBoxSampleItemViewModel() { Name = "Categorie 4" };
Categories.Add(cat1);
Categories.Add(cat2);
Categories.Add(cat3);
Categories.Add(cat4);
this.Selected = cat3;
}
internal void SelectByName(string p)
{
this.Selected = this.Categories.Where(s => s.Name.Equals(p)).FirstOrDefault();
}
}
And this Item ViewModel
public class ComboBoxSampleItemViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
NotifyPropertyChanged("Name");
}
}
}
If Combobox is bound to object class of the View Model, while the SelectionBoxItem of the sender object (in the SelectionChanged in code behind) has not that type, it means it's still loading.
ComboBox combo = sender as ComboBox;
if (combo.SelectionBoxItem.GetType() == typeof(BindedClass))
{
// Not loading
}