The View is:
<Controls:SplitButton Margin="217,409.75,56,185" Name="SplitButton1"
Width="384"
HorizontalAlignment="Center"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Orientation="Vertical"
DisplayMemberPath ="UserName"
SelectedItem="{Binding SelectedUser,UpdateSourceTrigger=PropertyChanged,Mode=OneWay}"
ItemsSource="{Binding Users, Mode=TwoWay}" />
The ViewModel is:
public string SelectedUser
{
get { return selectedUser; }
set
{
selectedUser = value;
RaisePropertyChanged("SelectedUser");
}
}
public ObservableCollection<UserModel> Users
{
get
{
return users;
}
set
{
users = value;
}
}
the Model is:
public class UserModel
{
private int id;
private string userName;
private int groupId;
private string deviceMacAddress;
public int Id { get; set; }
public string UserName { get; set; }
public int GroupId { get; set; }
public string DeviceMacAddress { get; set; }
}
i use the above code in xaml to bind the selectedItem in the splitbutton to ViewModel->property--SelectedUser.
but it does not work. anyone knows why?
SelectedUser is returned as Model name (PresentationLayer.Model.UserModel) instead of UserName prooperty.
Because your binding is OneWay by your definition.
Set your binding to TwoWay.
<Controls:SplitButton SelectedItem="{Binding SelectedUser,Mode=TwoWay}"/>
And, there is no need to set the UpdateSourceTrigger=PropertyChanged in this case, because the UpdateSourceTrigger is PropertyChanged by default for the SelectedItem property.
Related
I am new to MVVM and WPF and I am a little bit confused.
Let's say I have 2 models, like this:
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
}
and
public class Product
{
public int Id { get; set; }
public Category Category { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
I am not sure what is the recommended way of implementing the ProducViewModel class for adding a new Product:
Is it OK to just use a public property of type Product and bind it to the view, like this:
public class ProductViewModel : Screen
{
private Product _product;
public BindableCollection<Category> Categories;
public ProductViewModel(Product product, BindableCollection<Category> categories)
{
_product = product;
Categories = categories;
}
public Product Product
{
get { return _product }
set
{
_product = value;
SetAndNotify(ref _product, value);
}
}
}
In this case I will use the Category collection in the form to choose a category.
My view should look something like this:
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Label Content="Catgory" />
<ComboBox
ItemsSource="{Binding Categories}"
DisplayMemberPath="Name"
SelectedValue="?????? What comes here? "/>
</StackPanel>
</StackPanel>
Is this correct?
And also, how I will bind Product.Category in a ComboBox in the view?
Thanks!
In your View Model you should expose properties that are needed in your view. This case you can bind directly to your Product.Category or create a new property in your view model, and i think this last option is well suited in a MVVM scenario.
In your case you should create a selected category property your ProductViewModel:
public Category SelectedCategory { get { return _selectedCategory; }
set
{
_selectedCategory = value;
SetAndNotify(ref _selectedCategory, value);
Product.Category = _selectedCategory;
} }
And then in your xaml, you bind combobox's selected value to this new property:
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Label Content="Catgory" />
<ComboBox
ItemsSource="{Binding Categories}"
DisplayMemberPath="Name"
SelectedValue="{Binding SelectedCategory}"/>
</StackPanel>
I have a combobox that has an items source of type ObservableCollection<Clinic>
<ComboBox ItemsSource="{Binding Source={StaticResource ClinicList}}" DisplayMemberPath="Name" SelectedValue="{Binding Path=Name}" SelectedValuePath="Name"></ComboBox>
This combobox is within a ListView that is bound from EmployeeClinics.
public class Employee{
public ObservableCollection<Clinic> EmployeeClinics { get; set; }
}
When I launch the app I see the appropriate clinics. And the drop down seems to show the correct options, but when I update them, only the Name updates and not the ClinicId (it keeps previous ClinicId).
Edit: Similarly when I add a new clinic to the list and select it from the options, it's Id is 0 when I look at the collection.
Here is my clinic model.
public class Clinic {
public int ClinicId { get; set; }
public string _name { get; set; }
public string Name {
get {
return _name;}
set {
if (_name != value) {
_name = value;
}
}
}
}
UPDATE: Thanks #AyyappanSubramanian. I am making headway. I have updated my Objects
public class Employee{
public ObservableCollection<ClinicView> EmployeeClinics { get; set; }
}
public class ClinicView {
private Clinic selectedClinic;
public Clinic SelectedClinic {
get { return selectedClinic; }
set {
selectedClinic = value;
selectedClinicId = selectedClinic.ClinicId;
}
}
private int selectedClinicId;
public int SelectedClinicId {
get { return selectedClinicId; }
}
}
XAML:
<ComboBox ItemsSource="{Binding Source={StaticResource ClinicList}}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedClinic}"></ComboBox>
Changing the drop downs now properly changes the underlying object and updates the list as desired. Now my only issue is that the comboboxes don't display the current object, just show as blank on start. I've messed around with SelectedValue and Path with no luck. Any suggestions?
Refer the below code. You can use SelectedItem to get both the ID and Name in one SelectedObject. Get only ID using SelectedValue.
<ComboBox ItemsSource="{Binding Clinics}" DisplayMemberPath="ClinicName"
SelectedValuePath="ClinicId" SelectedValue="{Binding SelectedClinicId}"
SelectedItem="{Binding SelectedClinic}"/>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
class Clinic
{
public int ClinicId { get; set; }
public string ClinicName { get; set; }
}
class ViewModel
{
public ObservableCollection<Clinic> Clinics { get; set; }
public ViewModel()
{
Clinics = new ObservableCollection<Clinic>();
for (int i = 0; i < 10; i++)
{
Clinics.Add(new Clinic() { ClinicId=i+1,ClinicName="MyClinic"+(i+1) });
}
}
private int selectedClinicId;
public int SelectedClinicId
{
get { return selectedClinicId; }
set
{
selectedClinicId = value;
}
}
private Clinic selectedClinic;
public Clinic SelectedClinic
{
get { return selectedClinic; }
set
{
selectedClinic = value;
MessageBox.Show("ID:"+selectedClinic.ClinicId.ToString()+" "+"Name:"+selectedClinic.ClinicName);
}
}
}
I have a ListView which is populated with a collection called Files(ObservableCollection FileViewModel ), also I have another SelectedFiles(List Guid ) which hold the selected files id in it, how can I bind this to the UI to show the selected files with checkbox control.
Xaml:
<ListView Grid.Column="0" Grid.Row="2" Name="lstSourceFiles" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<CheckBox></CheckBox>
<TextBlock Text="{Binding Name}"></TextBlock>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code:
public List<Guid> SelectedSourceFiles { get; set; }
public ObservableCollection<FileViewModel> Files { get; set; }
public class FileViewModel
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public partial class MainWindow : Window
{
public List<Guid> SelectedSourceFiles { get; set; }
public MainWindow()
{
AddHandler(TreeViewItem.SelectedEvent, new RoutedEventHandler(TreeItemSelected), true);
}
private void TreeItemSelected(object sender, RoutedEventArgs e)
{
var item = e.OriginalSource as TreeViewItem;
if (item == null)
{ return; }
var folder = item.DataContext as FolderViewModel;
if (folder == null)
{ return; }
if (!folder.IsFilesLoaded)
{
FileManager.LoadFiles(folder);
}
lstSourceFiles.ItemsSource = folder.Files;
}
}
The easiest way would be to add an "IsSelected" property to the view model:
public class FileViewModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
Loop through and set the property:
foreach (var file in folder.Files)
file.IsSelected = (SelectedSourceFiles.Contains(file.Guid);
And then of course bind to it:
<CheckBox IsChecked="{Binding IsSelected}" />
An alternate to the above would be to bind to the GUID and use IValueConverter that checks against the the selected list.
I have a ListBox within a DataTemplate of another ListBox simplified to the following XAML
<ListBox ItemsSource="{Binding MovieList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<ListBox ItemsSource="{Binding Cast}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedCastMember, Mode=TwoWay}"/>
<TextBlock Text="{Binding MovieName}"/>
<TextBlock Text=....../>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The Parent List is bound to an ObservableCollection of Movie with each Movie object in turn having an ObservableCollection of cast members bound to a list box. The ViewModel property and Class below
public const string MovieListPropertyName = "MovieList";
private ObservableCollection<Movie> _movieList;
public ObservableCollection<Movie> MovieList
{
get
{
return _movieList;
}
set
{
if (_movieList == value)
{
return;
}
RaisePropertyChanging(MovieListPropertyName);
_movieList = value;
RaisePropertyChanged(MovieListPropertyName);
}
}
public const string SelectedCastMemberPropertyName = "SelectedCastMember";
private MovieCastMember _selectedCastMember;
public MovieCastMember SelectedCastMember
{
get
{
return _selectedCastMember;
}
set
{
if (_selectedCastMember == value)
{
return;
}
RaisePropertyChanging(SelectedCastMemberPropertyName);
_selectedCastMember = value;
RaisePropertyChanged(SelectedCastMemberPropertyName);
}
}
With the Movie and MovieCastMember classes as follows
public class Movie
{
public int Id { get; set; }
public string Name { get; set; }
public int Year { get; set; }
public string Overview { get; set; }
public double VoteAverage { get; set; }
public ObservableCollection<MovieCastMember> Cast { get; set; }
public BitmapImage PosterImage { get; set; }
}
public class MovieCastMember
{
public int Id { get; set; }
public string Name { get; set; }
}
I want to select a cast member in any of the Movie Lists and bind the MovieCastMember object to a property in my ViewModel. My List boxes populates fine, I have tried various scenarios in XAML but the SelectedItem are not updating the property in the ViewModel. Any help would be appreciated.
That's because you're binding against Movie class in second listBox. There is no "SelectedCastMember". Move it to Movie class and it will work.
I have a wpf combobox bound to a property LogicalP of a class SInstance. The ItemSource for the combobox is a dictionary that contains items of type LogicalP.
If I set LogicalP in SInstance to an initial state, the combobox text field shows empty. If I select the pulldown all my dictionary values are there. When I change the selection LogicalP in SInstance gets updated correctly. If I change Sinstance in C# the appropriate combobox value doesn't reflect the updated LogicalP from the pulldown.
I've set the binding mode to twoway with no luck. Any thoughts?
My Xaml:
<UserControl.Resources>
<ObjectDataProvider x:Key="PList"
ObjectType="{x:Type src:MainWindow}"
MethodName="GetLogPList"/>
</UserControl.Resources>
<DataTemplate DataType="{x:Type src:SInstance}">
<Grid>
<ComboBox ItemsSource="{Binding Source={StaticResource PList}}"
DisplayMemberPath ="Value.Name"
SelectedValuePath="Value"
SelectedValue="{Binding Path=LogicalP,Mode=TwoWay}">
</ComboBox>
</Grid>
</DataTemplate>
My C#:
public Dictionary<string, LogicalPType> LogPList { get; private set; }
public Dictionary<string, LogicalPType> GetLogPList()
{
return LogPList;
}
public class LogicalPType
{
public string Name { get; set; }
public string C { get; set; }
public string M { get; set; }
}
public class SInstance : INotifyPropertyChanged
{
private LogicalPType _LogicalP;
public string Name { get; set; }
public LogicalPType LogicalP
{
get { return _LogicalP; }
set
{
if (_LogicalP != value)
{
_LogicalP = value;
NotifyPropertyChanged("LogicalP");
}
}
}
#region INotifyPropertyChanged Members
#endregion
}
They are not looking at the same source.
You need to have SInstance supply both the LogPList and LogicalP.
_LogicalP is not connected to LogPList
If you want to different objects to compare to equal then you need to override Equals.
Here's my working solution. By moving the dictionary retrieval GetLogPList to the same class as that supplies the data (as suggested by Blam) I was able to get the binding to work both ways. I changed binding to a list rather than a dictionary to simplify the combobox
Here's the updated Xaml showing the new ItemsSource binding and removal of the SelectedValuePath:
<DataTemplate DataType="{x:Type src:SInstance}">
<Grid>
<ComboBox ItemsSource="{Binding GetLogPList}"
DisplayMemberPath ="Name"
SelectedValue="{Binding Path=LogicalP,Mode=TwoWay}">
</ComboBox>
</Grid>
</DataTemplate>
I then changed the dictionary LogPList to static so that it would be accessible to the class SInstance:
public static Dictionary<string, LogicalPType> LogPList { get; private set; }
Finally, I moved GetLogPList to the class SInstance as a property. Note again it's returning a list as opposed to a dictionary to make the Xaml a little simpler:
public class SInstance : INotifyPropertyChanged
{
public List<LogicalPType> GetLogPList
{
get { return MainWindow.LogPList.Values.ToList(); }
set { }
}
private LogicalPType _LogicalP;
public string Name { get; set; }
public LogicalPType LogicalP
{
get { return _LogicalP; }
set
{
if (_LogicalP != value)
{
_LogicalP = value;
NotifyPropertyChanged("LogicalP");
}
}
}
#region INotifyPropertyChanged Members
#endregion
}