I use MVVM pattern in my WPF application.
I have ObservableCollection Records in my ViewModel.
public enum RecordState
{
NotChanged,
Changed,
Added,
Deleted,
AlreadyExist
}
public class Record
{
public string FirstId { get; set; }
public RecordState State { get; set; }
public string CurrentId
{
get { return GetIdFromInstance(Instance); }
}
public MyStronglyTypedClass Instance { get; set; }
}
public class MyViewModel
{
public ObservableCollection<Record> Records;
// other code
}
In View i have DataGrid.
<DataGrid ItemsSource="{Binding }" //>
What i have to write(if that possible) in ItemsSource="{Binding /* here */}", so that Datagrid Items changed to
Records[0].Instance
Records[1].Instance
Records[2].Instance
...
Records[Records.Count-1].Instance
{Binding} means that your ItemsSource is the DataContext of that DataGrid(probably inherited from ancestor elements).
What you should do is set the DataContext of your top-level element(Window, UserControl etc..) to your ViewModel class.
And then as Gary suggested:
<DataGrid ItemsSource="{Binding Records}">
The link you gave about dynamic elements does the same in that matter, it adds more complicated element bindings and DataTemplates.
Related
I'm using the MVVM pattern with WPF and have run into a problem, which I can simplify to the following:
I have a CardType model.
public class CardType
{
public int Id { get; set; }
public string Name { get; set; }
}
And I have a viewmodel that consumes CardType.
public class ViewModel : INotifyPropertyChanged
{
private CardType selectedCardType;
public CardType SelectedCardType
{
get { return selectedCardType; }
set
{
selectedCardType = value;
OnPropertyChanged(nameof(SelectedCardType));
}
}
public IEnumerable<CardType> CardTypes { get; set; }
// ... and so on ...
}
My XAML has a ComboBox that bases its items on CardTypes and should preselect an item based on SelectedCardType.
<ComboBox ItemsSource="{Binding CardTypes}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedCardType}"/>
For reasons outside of my control, the SelectedCardType object will be a reference-unequal copy of the item in CardTypes. Therefore WPF fails to match the SelectedItem to an item in ItemsSource, and when I run the app, the ComboBox initially appears with no item selected.
I tried overriding the Equals() and GetHashCode() methods on CardType, but WPF still fails to match the items.
Given my peculiar constraints, how can I get ComboBox to select the correct item?
You might not be overriding Equals and GetHashCode properly. This should work for you. (However, just overriding Equals will work in your case but it's considered to be good practice to override GetHashCode too when you override Equals for a class)
public class CardType
{
public int Id { get; set; }
public string Name { get; set; }
public override bool Equals(object obj)
{
CardType cardType = obj as CardType;
return cardType.Id == Id && cardType.Name == Name;
}
public override int GetHashCode()
{
return Id.GetHashCode() & Name.GetHashCode();
}
}
You can use SelectedValue and SelectedValuePath:
<ComboBox ItemsSource="{Binding CardTypes}"
DisplayMemberPath="Name"
SelectedValue="{Binding ProductId, Mode=TwoWay}"
SelectedValuePath="Id"/>
Where ProductId is a int property with NotifyPropertyChanged.
Read a great explanation here:
Difference between SelectedItem, SelectedValue and SelectedValuePath
A workaround that you could do is to bind your SelectedItem to a string (instead of cardType), then create an object of type CardType using this string?
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
}
I have two datagrids in a single view but the collections which are ItemsSource of these datagrids are in different View Models. So is it possible to bind these two datagrids with the collections in two different View Models?
Go for a view model combining both:
public class ViewModelA {
public ObservableCollection<CustomClass> Items { get; set; }
/* properties, etc. */
}
public class ViewModelB {
/* properties, etc. */
}
public class CombiningViewModel {
public ViewModelA A { get; set; }
public ViewModelB B { get; set; }
}
Binding can be done like
<DataGrid ItemsSource="{Binding A.Items}">
<!-- Sample, not complete -->
</DataGrid>
No, not directly. You do have options though:
You could set the DataCOntext of the view to itself, then expose each viewmodel through a separate property and bind to those properties:
public class MyView : Window
{
public MyView()
{
this.DataContext = this;
}
public ViewModel1 FirstViewModel { get; set; }
public ViewModel2 SecondViewModel { get; set; }
}
Or you could make a wrapper viewmodel which either extends (inherits from) one of the viewmodels, or wraps them both and surfaces the appropriate properties:
public class MyCompositeViewModel
{
public ViewModel1 FirstViewModel { get; set; }
public ViewModel2 SecondViewModel { get; set; }
}
You can set the DataContext for each DataGrid rather than for the container view.
<Grid>
<DataGrid ... DataContext="..." />
<DataGrid ... DataContext="..." />
</Grid>
Or don't use a DataContext and Bind to the models directly
<DataGrid ItemsSource="{Binding Source={StaticResource ...}}" />
I am using the MVVM pattern in my project;
In my project I have a CheckedComboBoxEdit then bind to a Person List;
Public Class Person
{
Public Int Id { get; set; }
Public string Name { get; set; }
}
When User select some Items in CheckedComboBoxEdit, how can I get CheckedComboBoxEdit SelectedItems in my ViewModel?
You need a property on your ViewModel that binds to the CheckedComboBoxEdit SelectedItems property. You should probably look at related DevExpress posts.
<CheckedComboBoxEdit x:Name="cbPeople" SelectedItems="{Binding SelectedPeople}" ... />
I have a simple example where I'm creating a View consisting of a list box, and the list box displays a bunch of items. I'm wondering if I'm going about the creation of the View Model and Model classes correctly here. Use whatever value of correctly works in this context, I understand it's a bit subjective, but my current solution doesn't feel right. Here's a simplified version.
The ViewModels and Models:
namespace Example
{
public class ParentViewModel
{
public ParentViewModel()
{
// ... Create/Consume ChildViewModel * n
}
public List<ChildViewModel> ChildViewModels { get; set; }
}
public class ChildViewModel
{
public ChildViewModel()
{
// ... Create/Consume ChildModel
}
public ChildModel Model { get; set; }
}
public class ParentModel
{
public List<ChildModel> ChildModels { get; set; }
public ParentModel()
{
// ... Create/Consume ChildModel * n;
}
}
public class ChildModel
{
public ChildModel()
{
// ... Contains actual data.
}
public string Data { get; set; }
}
}
The View:
<Window x:Class="Example.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Example="clr-namespace:Example" Title="View" Height="300" Width="300"
DataContext="{StaticResource TheViewModel}">
<Window.Resources>
<Example:ParentViewModel x:Key="TheViewModel" />
</Window.Resources>
<Grid>
<ListBox Height="261" HorizontalAlignment="Left" Name="listBox1" VerticalAlignment="Top" Width="278" ItemsSource="{Binding ChildViewModels}"/>
</Grid>
In the proper code, the listbox will use a data template to display the child view models. But as you can see I'm not sure how the to instantiate the child related objects. It feels like the ParentViewModel will have a reference to the ParentModel and create ChildViewModel objects based on the ParentModel's ChildModel objects. Now I've said that it doesn't sound so daft, but I'd be interested in your thoughts.
You are on the right track.
The parent model would naturally contain a list of child models, e.g. a customer having multiple orders.
When ParentViewModel is created and loaded by a third-party, it is passed a ParentModel. Then the ParentViewModel will:
Assign the ParentModel to a local variable
Create a ChildViewModel for each ChildModel by passing the
ChildModel to the ChildViewModel constructor
Add each of those ChildViewModels to a list
By the way, you want
public List<ChildViewModel> ChildViewModels { get; set; }
to be
public ObservableCollection<ChildViewModel> ChildViewModels { get; set; }