Silverlight bind collection to Combobox in DataForm using MVVM - silverlight

I have this problem, I've got Silverlight app written using MVVM. I need to create DataForm which is binded to property on ViewModel and I want to add ComboBox and fill it with values from other collection in the same ViewModel.
Code:
<dataFormToolkit:DataForm CurrentItem="{Binding NewUser, Mode=TwoWay}" AutoGenerateFields="False" Height="298">
<dataFormToolkit:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<dataFormToolkit:DataField Label="Email">
<TextBox Text="{Binding Email, Mode=TwoWay}"/>
</dataFormToolkit:DataField>
<dataFormToolkit:DataField Label="Język">
<ComboBox ItemsSource="{Binding Path=Languages, Mode=TwoWay}"/>
</dataFormToolkit:DataField>
</StackPanel>
</DataTemplate>
</dataFormToolkit:DataForm.EditTemplate>
</dataFormToolkit:DataForm>
All this is handled by NewAccountVM which has these properties:
private User newUser;
public User NewUser {
get
{
return newUser;
}
set
{
if (value != newUser)
{
newUser = value;
RaisePropertyChanged("NewUser");
}
}
}
private ObservableCollection<Language> languages;
public ObservableCollection<Language> Languages
{
get { return languages; }
set
{
if (languages != value)
{
languages = value;
RaisePropertyChanged("Languages");
}
}
}
Now, all this works besides adding ItemsSource to ComboBox. I've found many examples showing how fill CB in CodeBehind, but like I said I want to do this in MVVM-Style :)
I understand that, ComboBox inherited DataContext from DataForm, and this ItemsSource="{Binding Path=Languages, Mode=TwoWay}" will not work, but I have no idea how to achieve my goal.
Can somebody help me?

1) Declare the viewmodel to the view in the resources section.
<UserControl.Resources>
<local:MyViewModel x:Key="myViewModel" />
</UserControl.Resources>
2) Bind the ComboBox to the collection property on the viewmodel.
<ComboBox ItemsSource="{Binding Path=Languages,
Source={StaticResource myViewModel},
Mode=TwoWay}"/>

you can set the Data Context in XAML to your static resource like so:
<UserControl.DataContext>
<Binding Source="{StaticResource myViewModel}" />
</UserControl.DataContext>

Scenario A:
1. Assume you wish to populate a combo with all the membership Roles, and allow the client to select the role and assign to the User :
i.e. ObjectA : Aspnet_Role
i.e. ObjectB : User
Let us say User.MembershipRoleId is to be bound to Aspnet_Role.RoleId
Dataform is bound to ObjectB
Combobox in dataform is populated with List
In XAML write the following:
<Combobox DisplayMemberPath="RoleName"
SelectedValue="{Binding MembershipRoleId,Mode=TwoWay}" SelectedValuePath="RoleId" />
here the mapping is, ObjectB.MembershipRoleId=ObjectA.RoleId
Scenario B:
1. If you do not want to explicitly define by the way in ScenarioA, then in that case, define a ForeignKey-PrimaryKey relationship between the tables in the database like
ForeignKey -> User.MembershipId
PrimaryKey -> Aspnet_Roles.RoleId
2. From the ADO.NET (.edmx) file, update the model from the database, you will observe that in the User entity there is an association made upon entity Aspnet_Roles
3. In XAML write the code as below to bind the combobox, to the desired field of the Dataform
<Combobox DisplayMemberPath="RoleName" SelectedItem="{Binding MembershipRoleId,Mode=TwoWay}" .... />

Related

What is normally bound to a wpf combobox

I want to bind something to a combobox that will display something different than it's value.
What would be the suggested/most common data container to link the combobox to? Would it be a custom type and then do a list of that type? A data table? I am using vb.net and wpf. The list would be something like:
dog,1
cat,2
bird,3
fish,4
The combobox would display the animal name and the value would be the number. The data to populate the combobox will come from a MYSql database.
Set the DisplayMemberPath on your Combobox to the property of the underlying object you want to
display.
So assuming you have an object like this:
Public Class Animal
{
string Name {get;set;}
int Number {get;set;}
}
You would set your DisplayMemberPath to Name.
Else check out this link for a more complete answer:
Binding WPF ComboBox to a Custom List
You can populate with anything you want, you can also display multiple items like so
<ComboBox ItemsSource{Binding ItemList}>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding AnimalName}" />
<Checkbox IsChecked={Binding SelectAnimal}" Content="{Binding Age}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The Select animal is just so you can get an idea of doing a template

Binding TwoWay to SelectedItem: "Wrong way" synchronization on initialization

I am trying to bind a property of my DataContext to the SelectedItem on a ComboBox like this:
<ComboBox x:Name="ElementSelector"
ItemsSource="{Binding Source={StaticResource Elements}}"
DisplayMemberPath="ElementName"
SelectedItem="{Binding ValueElement, Mode=TwoWay}">
where the Elements resource is a CollectionViewSource (don't know, whether this matters).
When everything is initialized, the property ValueElement of the DataContext is set to the first item in the CollectionViewSource. What I want, is to initialize it the other way around: I would like to set SelectedItem of the ComboBox to the value of the property or null if no matching item is contained.
How can this be done?
EDIT - Additional information:
The ComboBox is part of a DataTemplate:
<DataTemplate x:Key="ReferenceTemplate"
DataType="viewModels:ElementMetaReferenceViewModel">
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<ResourceDictionary>
<views:ElementsForReferenceViewSource x:Key="Elements"
Source="{Binding DataContext.CurrentProject.Elements, ElementName=Root}"
ReferenceToFilterFor="{Binding}"/>
</ResourceDictionary>
</StackPanel.Resources>
<TextBlock Text="{Binding PropertyName}"/>
<ComboBox x:Name="ElementSelector"
ItemsSource="{Binding Source={StaticResource Elements}}"
DisplayMemberPath="ElementName"
SelectedItem=""{Binding ValueElement, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
The ElementsForReferenceViewSource simply derives from CollectionViewSource and implements an additional DependencyProperty which is used for filtering.
The DataContext of the items in the CollectionViewSource look like this:
public class ElementMetaReferenceViewModel : ViewModelBase<ElementMetaReference, ElementMetaReferenceContext>
{
...
private ElementMetaViewModel _valueElement;
public ElementMetaViewModel ValueElement
{
get { return _valueElement; }
set
{
if (value == null) return;
_valueElement = value;
Model.TargetElement = value.Model;
}
}
...
}
For people encountering the same issue
The above code works as expected. The solution was getting the stuff behind the scenes right. Make sure, that the instance of the ViewModel which is the value of the property you want to bind to is definitely contained in the CollectionViewSource.
In my case the issue was deserializing an object tree incorrectly, so objects were instantiated twice. Then for each object a distinct ViewModel was initialized and then obviously the value of the property was not contained in the list.
Remark
To check whether this is an issue in your case, you can try the following:
Override the ToString() methods of the ViewModels displayed in the ComboBox like this:
public override string ToString()
{
return "VM"+ Model.GetHashCode().ToString();
}
Then you can easily compare the items in the source collection with the value on your property. Not the most professional way, but it did the job for me.

WPF DataGrid TwoWay Binding

I have property UserSet which contains from ObservableCollection<GridRow>.
GridRow - my class, which contains 4 properties like this:
public int Id
{
get { return id; }
set
{
id = value;
RaisePropertyChanged("Id");
}
}
I populate UserSet, then Grid binds to it. When I change id field works setter Id. It sets right values.
But, after all changes, when I click other button my UserSet has not modified values. So I can't get updated Grid.
This is my XAML:
<DataGrid ItemsSource="{Binding UsersSet, Mode=TwoWay}" AutoGenerateColumns="True">
</DataGrid>
Please help.
You could try and set the UpdateSourceTrigger:
<DataGrid ItemsSource="{Binding UsersSet,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="True">
</DataGrid>
Without knowing the rest of your code it is pretty hard to guess.

How to get data into the ViewModel of a UserControl?

New to WPF/MVVM. I have a data object of type "MyData". One of its properties is of type "MySubsetData".
I show a collection of "MyData" objects in a datagrid.
<DataGrid ItemsSource="{Binding Path=MyDataCollection}">
<!-- Each row of the datagrid contains an item of type "MyData" -->
<DataGrid.Columns .../>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<local:MySubsetDataUserControl/>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
The row details should show the content of "MySubsetData". The view of the row details is in a separate user control (here: "MySubsetDataUserControl").
At the moment I don't set a view model for "MySubsetDataUserControl", so it inherits the data context from the parent's datagrid row.
<UserControl>
<!-- Namespace stuff not shown for simplicity -->
<Grid DataContext="{Binding Path=MySubsetData}">
<!-- Show the MySubsetData properties here -->
<!-- e.g. a textbox -->
<TextBox Text="{Binding Path=TextData, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</UserControl>
Altough this is working I face several problems with this approach:
All business logic will be in the user control parent's view model, where it simply doesn't belong. Making the view model messier than it need to be. Not to mention that command bindings in the user controls xaml look very ugly as well. It just doesn't feel right.
As more row details could be visible at the same time, I can't bind the properties of "MySubsetData" to an observable property in the view model. I.e. if I change a property in code (e.g. TextData) the change will not be reflected in the view. My workaround is not to alter the property "TextData". Instead I change the content of the textbox Text property, which in turn will update the "TextData" property. And that feels very wrong!
So I would like to use another view model for my user control, but I don't know how to access my data then.
<UserControl.DataContext>
<local:UserControlViewModel/>
</UserControl.DataContext>
How do I access "MySubsetData" now?
Let assume you have a view model like this:
public class ViewModel
{
public IEnumerable<MyData> MyDataCollection{get; private set;}
}
Where
public class MyData
{
public MySubsetData MySubsetData { get; }
}
Your view containing the DataGrid would be
<UserControl.DataContext>
<local:ViewModel>
</UserControl.DataContext>
<DataGrid ItemsSource="{Binding Path=MyDataCollection}">
<DataGrid.Columns .../>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<!-- each row of the items control has an implicit DataContext of MyData -->
<!-- so bind the DataContext of the subset control to MySubsetData -->
<local:MySubsetDataUserControl DataContext={Binding MySubsetData}/>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
Now your subset control can look like
<UserControl>
<Grid>
<TextBox Text="{Binding Path=TextData, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</UserControl>
On re-reading the question, maybe all I've done is repeat the Xaml from the question in a slightly different way.
However the various classes should be implementing INotifyPropertyChanged, e.g.
public class MySubsetData : INotifyPropertyChanged
{
public string TextData
{
get{...}
set{...; OnPropertyChanged("TextData"); }
}
}
Then the TextBox bound to the TextData property will reflect changes made in code.

Bbinding combobox within dataform to view model property outside dataform's context

I have two properties in my view model:
//Relationship has property ReasonForEndingId
private Relationship editRelationship;
public Relationship EditRelationship
{
get
{
return editRelationship;
}
set
{
if (editRelationship != value)
{
editRelationship = value;
RaisePropertyChanged(EditRelationshipChangedEventArgs);
}
}
}
//ReasonForLeaving has properties Reason & Id
private IList<ReasonForLeaving> reasonsComboList { get; set; }
public IList<ReasonForLeaving> ReasonsComboList
{
get
{
return reasonsComboList;
}
private set
{
if (reasonsComboList != value)
{
reasonsComboList = value;
RaisePropertyChanged(ReasonsComboListChangedEventArgs);
}
}
}
In my xaml I have the following: (specifically note the binding on the dataform and combobox)
<toolkit:DataForm x:Name="EditForm" CurrentItem="{Binding EditRelationship, Mode=TwoWay}">
<toolkit:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<toolkit:DataField>
<ComboBox x:Name="EndReasonCombo" ItemsSource="{Binding ReasonsComboList}" DisplayMemberPath="Reason" SelectedValuePath="Id" SelectedValue="{Binding ReasonForEndingId, Mode=TwoWay}"/>
</toolkit:DataField>
So, I'm trying to bind to a list that exists in my viewmodel (the datacontext for the page). However, the DataForm's datacontext is EditRelationship. ReasonsComboList does not exist within EditRelationship.
How can I bind the combobox so that it will display the list of items available in ReasonsComboList?
Thanks for your help!
Here's what I did (tested and works):
Within a DataForm this won't work (because its a DataTemplate) :
<ComboBox MinWidth="150" DisplayMemberPath="Name" Name="cbCompanies"
SelectedItem="{Binding TODOCompany,Mode=TwoWay}"
ItemsSource="{Binding ElementName=control, Path=ParentModel.Companies}" />
But you can do this instead:
<ComboBox MinWidth="150" DisplayMemberPath="Name" Name="cbCompanies"
SelectedItem="{Binding TODOCompany,Mode=TwoWay}"
Loaded="cbCompanies_Loaded"/>
Code behind:
private void cbCompanies_Loaded(object sender, RoutedEventArgs e)
{
// set combobox items source from wherever you want
(sender as ComboBox).ItemsSource = ParentModel.Companies;
}
if you set your datacontext using locator in this way
DataContext="{Binding FormName, Source={StaticResource Locator}}"
<ComboBox ItemsSource="{Binding FormName.ReasonsComboList, Source={StaticResource Locator}, Mode=OneWay}"
DisplayMemberPath="Reason" SelectedValuePath="Id"
SelectedValue="{Binding ReasonForEndingId, Mode=TwoWay}"/>
tested and working
I haven't tested this with your exact scenario, but you should be able to reference the DataContext of some parent element when binding the ItemsSource of the ComboBox. Basically using Silverlight's element-to-element binding to actually bind to some property on the parent container's DataContext instead of the current element's DataContext.
For example, if your main ViewModel was the DataContext of the LayoutRoot element you should be able to do something like this:
<ComboBox x:Name="EndReasonCombo" ItemsSource="{Binding DataContext.ReasonsComboList, ElementName=LayoutRoot}" DisplayMemberPath="Reason" SelectedValuePath="Id" SelectedValue="{Binding ReasonForEndingId, Mode=TwoWay}"/>
Creating a Silverlight DataContext Proxy to Simplify Data Binding in Nested Controls
Disclaimer: This may not actually work for the DataForm, but is suitable for the same problem when using a DataGrid. but I'm putting it here as an answer because it was an interesting read and helped me understand some things when I experienced the same problem.

Resources