I'm using WPF and MVVM, and have a support ticket window that has cascading ComboBoxes as follows. The first is bound to an ObservableCollection<ProblemCode> on the view model. The ProblemCode objects have a self-referencing property to their child codes, down to a level of four codes. The XAML for the ComboBoxes looks like this (simplified, and only three shown for brevity)...
<ComboBox ItemsSource="{Binding ElementName=Root, Path=DataContext.ProblemCodes, Mode=TwoWay}"
Name="ProblemCodeLevel1"
DisplayMemberPath="Description"
SelectedValuePath="ID"
SelectedValue="{Binding ProblemCode1ID, Mode=TwoWay}" />
<ComboBox ItemsSource="{Binding ElementName=ProblemCodeLevel1, Path=SelectedItem.Children}"
Name="ProblemCodeLevel2"
DisplayMemberPath="Description"
SelectedValuePath="ID"
SelectedValue="{Binding ProblemCode2ID, Mode=TwoWay}" />
<ComboBox ItemsSource="{Binding ElementName=ProblemCodeLevel2, Path=SelectedItem.Children}"
Name="ProblemCodeLevel3"
DisplayMemberPath="Description"
SelectedValuePath="ID"
SelectedValue="{Binding ProblemCode3ID, Mode=TwoWay}" />
When I load a window for a new ticket, the first ComboBox is correctly populated. Selecting an item populates the second and so on. When I save the ticket, the data is correctly saved.
However, when I save the ticket and reopen the window, only the first ComboBox has the selected item set. The other ComboBoxes don't have anything set.
I guess that the first ComboBox is set as the data is available when the data binding takes place. At that stage, as the first ComboBox is data bound, the second one doesn't yet have any items, so doesn't get bound. Same for the third and so on.
Anyone any suggestions as to how to get the binding working? I probably could hack this by adding code to catch various events, but apart from breaking the MVVM pattern, it sounds like none of those situations that would end up convoluted and buggy.
Generally speaking you shouldn't bind directly to elements, you should be binding to properties in your view model. That way you know the property notification is being done properly and you can add breakpoints etc to confirm the bindings are all working as well. In this particular case you need to add something like SelectedItem="{Binding Level1Item}" to your first ComboBox and then add a property for it in your view model:
public ProblemCode _Level1Item;
public ProblemCode Level1Item
{
get { return this._Level1Item; }
set
{
if (this._Level1Item != value)
{
this._Level1Item = value;
RaisePropertyChanged(() => this.Level1Item);
}
}
}
Then your second ComboBox binds to this property instead of Element.SelectedItem.Children:
<ComboBox ItemsSource="{Binding Level1Item.Children}"
...etc...
Repeat for the second and third ComboBoxes and you'll have the functionality you're after.
Related
While trying to convert usual wpf fields to the custom fields that the program I need to modify, I came across an issue of having 2 different data sources.
1) The data source that retrieves/inserts data to fill this combobox (DataSource)
2) The data source that takes care of other UI elements (DSP)
As when certain items are selected from the combobox, not only does it get stored with the other information in the form, but it may show/hide another UI element.
I am trying to convert:
<ComboBox Name="tempComboBox"
ItemsSource="{Binding Source={StaticResource DataSource}, Path=Value.Properties[temp].MetaData.Lookups}"
DisplayMemberPath="Description"
SelectedValuePath="Value"
SelectedValue="{Binding Source={StaticResource DSP}, Path=Value, ValidatesOnDataErrors=True}"
Style="{StaticResource tempComboStyle}"/>
Into something like this:
<ctrls:Fields Name="tempComboBox"
FieldName="temp"
DataContext="{Binding Source={StaticResource DataSource}, Path=Value, ValidatesOnDataErrors=True}"
Style="{StaticResource tempComboStyle}"/>
However, this WILL NOT work as it only stores the data, but does not hide/show elements when the specific item is selected.
I have tried multi binding, which does not work. Surrounding the combobox tags with the ctrls:Fields tag, again doesn't work. And combining the DataContext property with both SelectedValue and ItemSource, none of which work.
I do not have any way of getting to the code behind of this form either, it must be strictly done through XAML.
Thank you for any help!
I a have a View Players, the datacontext is set to a ObservableCollection Players from the ViewModel MainPlayerViewModel.
In the View I have a datagrid with columns TeamId, Name and Position.
I want to bind the TeamId column with a combobox to a list of available teams from the MainTeamViewModel which has a collection property Teams but of course I want the MainPlayerViewModel to be updated whenever I update the team for a player.
I hope you can follow me here..
This is my xaml:
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox DataContext="{Binding MainTeam, Mode=OneWay, Source={StaticResource Locator}}"
Height="23" HorizontalAlignment="Left"
Name="cmbTeams" VerticalAlignment="Top" Width="100" ItemsSource="{Binding Teams,
Mode=TwoWay}" SelectedValue="{Binding Path=Model.teamid, Mode=TwoWay}"
DisplayMemberPath="Model.teamid"/>
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
When I edit the cell it shows the list of available teams but the selectedvalue I pick from the list doesn't turn up in the TeamId column
How do I pull this off?
Kind regards,
Mike
UPDATE:
Despite the help I received I didn't get it to work binding one View to 2 different Viewmodels.
Guess the solution offered is long above my head..
I couldn't set the datacontext of the datagrid to MainTeam because it has an ItemsSource of players and a selecteditem bound twoway to selectedplayer.
Anyway I decided to keep it 1 View / 1 ViewModel and created a public property on my PlayerViewModel named teamsVM:
public MainTeamViewModel teamsVM
{
get
{
return ViewModelLocator.Container.Resolve<MainTeamViewModel>();
}
}
Now I can set the Itemsource to this new property and my player row get's updated when I change teams:
<DataTemplate>
<ComboBox
Height="23" HorizontalAlignment="Left"
Name="cmbTeams" VerticalAlignment="Top" Width="100"
ItemsSource="{Binding teamsVM.Teams,
Mode=TwoWay}" SelectedValue="{Binding Model.teamid, Mode=TwoWay}"
DisplayMemberPath="Model.teamid" SelectedValuePath="Model.teamid"/>
</DataTemplate>
Regards,
Mike
I find two things wrong with this code.
You are missing the SelectedValuePath for the ComboBox. Even though you bind all teams to it, the selected item's id is null because the SelectedValuePath is missing.
You also have a DataContext and an ItemsSource. Use only the ItemsSource for the teams you want to display, and the SelectedValue to be bound to the player's teamId, unless your view model has a "Teams" property and a "Player" property, in which case the DataContext may be used. (Id set the DataContext in code though...)
So yo will end up with something like this:
ItemsSource="{Binding Teams, Mode=TwoWay}" //Bind to all teams.
SelectedValue="{Binding Player, Path=TeamId, Mode=TwoWay}" //Bind to the teamId of the player.
DisplayMemberPath="TeamName" //that's the Name of each team.
SelectedValuePath="TeamId" //that's the Id of the team.
Two problems here:
First, as #bleepzer noted you did not specify the value/display paths in your combo box.
Second, you trying to access a property in the data context that is outside your grid (i.e. the main view model's data context) from within a data template. In silverlight 4 there is no relative source binding (something you would use in SL 5 or WPF), so you will have to use element binding to archive what you want.
Here is an example based on your code. It is not complete as it leaves out some of the DataGrid elements needed, but it shows the concept:
<data:DataGrid x:Name="myDataGrid"
DataContext="{Binding MainTeam, Mode=OneWay, Source={StaticResource Locator}}" >
<!-- additional stuff needed here -->
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox Height="23" HorizontalAlignment="Left"
Name="cmbTeams" VerticalAlignment="Top" Width="100"
ItemsSource="{Binding ElementName=myDataGrid, Path=DataContext.Teams}"
SelectedValuePath="TeamId"
DisplayMemberPath="TeamName"
SelectedValue="{Binding Path=Model.teamid, Mode=TwoWay}"/>
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
<!-- additional stuff needed here -->
<data:DataGrid>
And here is the description:
Add a name to your data grid.
Make sure the data grid has the right data context, either by setting it explicitly as in the sample, or inheriting it from the parent hierarchy.
Modify your ComboBox's ItemsSource property to point to the data grid using the element name you specified earlier. As you are now on the element and not on the data context you have to use DataContex.Teams to access the Teams property on the data context of your grid. The ItemsSource does not need two-way-binding as the view does not write anything back to your view model.
Specify the SelectedValuePath and DisplayMemberPath properties.
Finally, bind the SelectedValue property of the combo box to your rows model TeamId property using two-way-binding - needed now as the view should update the model's value. Important: the SelectedValue property of the combo box has to be bound after the ItemsSource to prevent some problems with the combo box.
In the below window, the Existing Reports combo is bound to an observeablecollection of reportObjects. I have a reportObject property currentReport bound to the combo's SelectedValue property, OneWay. However, that's not working when bound in XAML.
SelectedValue="{Binding currentReport, Mode=OneWay}"
TwoWay binds fine, but I can't do it that way without writing an undo() method to the reportObject class. I'm binding the currentReport's properties to the various textboxes for editing. I want to bind OneWay so the source doesn't get changed. The currentReport's properties are all TwoWay bound to the corresponding textboxes so when I update the table in SQL [Save], it'll pull from that object, who's data is current.
<TextBox Text="{Binding currentReport.reportName, Mode=TwoWay}"
All of the properties bound from currentReport to the textboxes work fine as well. The only problem is the OneWay binding from the SelectedValue to the currentReport object. Does anyone have any ideas how to get this to work? I saw there was a bug, but the post I saw was 2009.
Sorry about the yellow. Not my idea. =)
EDIT: Added this XAML just in case.
<ComboBox ItemsSource="{Binding reportsCollection}" SelectionChanged="cboReports_SelectionChanged"
DisplayMemberPath="displayName"
SelectedValue="{Binding currentReport, Mode=TwoWay}"
x:Name="cboReports" Width="342" Height="40" VerticalAlignment="Center"/>
Forget about you need to change values - that is a separate problem - need to review your data design. Start with the UI problem question. If you want a user to be able to select an item from a combo box then it must have two way binding. Your first question is SelectedValue="{Binding currentReport, Mode=OneWay}" is failing why?
I have my silverlight app which pulls data into a datagrid from a view model. The vm is exposed via Mef. I also have a details grid which has comboboxes. The vm also contains the data to populate the combobox values. Upon first load, everything works fine and the selected items on te comboboxes are correct and I can select alternative values. However, if I sort my main data grid (allow sort=true) then I find the binding for selected value on the comboboxes dissapear. The combobox is still populated with data but nothing is selected.
Has anyone come across this issue before? I am unsure how to solve this one.
Thanks
Shaggy, I just noticed this the other day trying to setup async ComboBox loading. For some reason the ComboBox just appears to drop the binding (but you already knew that). Anyway, I put this post together that addresses some of these issues. Let me know if it helps.
http://blogs.msdn.com/kylemc/archive/2010/06/18/combobox-sample-for-ria-services.aspx
Kyle
How are you gathering the Data for the combobox? Is it a list of strings or a list of specific objects? What could be occuring is that the sorting is creating another set of objects within its combobox, or each row of data, and the selected item is no longer matching the reference. Could you post a code example?
Code for the comboboxes is as follows
<TextBlock>Status</TextBlock>
<ComboBox x:Name="CB_Status" ItemsSource="{Binding Status}" SelectedValuePath="StatusId" SelectedValue="{Binding CurrentCall.StatusId, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" ItemTemplate="{StaticResource StatusTemplate}" />
<TextBlock>Priority</TextBlock>
<ComboBox x:Name="CB_Priority" ItemsSource="{Binding Priorities}" SelectedValuePath="PriorityId" SelectedValue="{Binding CurrentCall.PriorityId, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" ItemTemplate="{StaticResource PriorityTemplate}"/>
<TextBlock>Issue Type</TextBlock>
<ComboBox x:Name="CB_IssueType" ItemsSource="{Binding IssueType}" SelectedValuePath="IssueTypeId" SelectedValue="{Binding CurrentCall.IssueTypeId, Mode=TwoWay,NotifyOnValidationError=True, ValidatesOnExceptions=True}" ItemTemplate="{StaticResource IssueTemplate}" />
The data is pulled from a VM, and the data is called using async calls at the start, and the variables are populated as follow:
private IEnumerable<Priority> _priorities;
public IEnumerable<Priority> Priorities
{
get { return _priorities; }
set
{
if (value != _priorities)
{
_priorities = value;
this.RaisePropertyChanged("Priorities");
}
}
}
private IEnumerable<Status> _status;
public IEnumerable<Status> Status
{
get { return _status; }
set
{
if (value != _status)
{
_status = value;
this.RaisePropertyChanged("Status");
}
}
}
private IEnumerable<IssueType> _issueType;
public IEnumerable<IssueType> IssueType
{
get { return _issueType; }
set
{
if (value != _issueType)
{
_issueType = value;
this.RaisePropertyChanged("IssueType");
}
}
}
so comboboxes are IEnumerable collections of various entities. The thing is upon sorting, the parent table, the combo boxes lose their selected value, but the data for the combobox remains intact. Via fiddler I can see that there arent any subsequent calls to fetch the data for the comboboxes.
One thought and issue I have had with SelectedValue before, is that when a combox, datagrid, etc... go through state changes like; Loss of focus, Redrawing, and a few other they will change the SelectedValue to null. It is possible, that when you choose a value the SelectedValue (bound properties) on the VM are set. However, when the Grid sorts it also tells the VM to set the SelectedValue to 'null'. So, after the sort the comboboxes are set to default values.
A thing you can try, is set a breakpoint at one of the SelectedValue properties 'set' and see how often the value is set, during Debug.Assert if the value is null.
Not sure of your setup here, but if your datagrid is a list of calls and CurrentCall is the selected item can you not use Element Binding? E.g.
<ComboBox x:Name="CB_Status"
ItemsSource="{Binding Status}"
SelectedItem="{ Path=SelectedItem.Status, Mode=TwoWay, ElementName=YOUR_DATAGRID}"
ItemTemplate="{StaticResource StatusTemplate}" />
I assume the grid’s datacontext is bound to IEnumerable<Call> (or something) on your VM so I’d say a sort would result in a new collection (like if you said .Sort or order etc).
Here is a quick cut from a working example (using a listbox not datagrid in this case)
<ComboBox
DisplayMemberPath="DisplayName"
SelectedItem="{Binding Path=SelectedItem.Individual.IndividualNameTitle,
Mode=TwoWay, ElementName=AccountList}"
ItemsSource="{Binding Path=IndividualNameTitles}">
</ComboBox>
Hope it helps.
I have a datagrid. A column of the datagrid is a simple <DataGridTemplateColumn> with its CellTemplate containing a <DataTemplate> which contains a <ComboBox> such as
<my:DataGrid Name="dataGridMain" AutoGenerateColumns="False">
<my:DataGrid.Columns>
<my:DataGridTemplateColumn Header="Food" >
<my:DataGridTemplateColumn.CellTemplate >
<DataTemplate>
<ComboBox Name="comboDataTemplate"
Text="{Binding Path=Food,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Source={StaticResource resFoodLookups}}"
DisplayMemberPath="FoodName"
SelectedValuePath="FoodID" IsEditable="True" />
</DataTemplate>
</my:DataGridTemplateColumn.CellTemplate>
</my:DataGridTemplateColumn>
</my:DataGrid.Columns>
</my:DataGrid>
All is working fine. Each combobox is bound to a static list due to the ItemsSource="{Binding Source={StaticResource resFoodLookups}}" statement.
But my requirement is that this list will change from row-to-row.
That is: each time a user types a new entry in the combobox list on one row, I want to have it available in the selection on the next row.
Basically, I want to create a new list for the user each time the user inserts a new word in the combobox on any of the rows. (The combobox is editable).
Now, I can wire up the "ItemsSource=..." at run-time, but I'm only able to do this once thus the <DataTemplate> propagates the 'same' list to 'all' the comboboxes on 'all' the rows.
My thoughts are that I need to change the ItemsSource=... property on an object-by-object basis on each combobox that is created in memory after the DataTemplate has created them - but I have no idea how to do this.
What you need to do is perform 2 way data binding to your the ItemsSource, this way when the ItemSource is updated in one of the combo boxes it will auto update your original collection and therefore your other combo boxes as well.
What I normally do is use the MVVM pattern. It is worth some research if you are not already using a particular pattern on your application.
Using it to solve your problem i would do the following:
Create a ViewModel (Lets call it MyViewModel) which has a collection of values called 'MyComboBoxItems' (It is important that you use ObservableCollection for the databinding to work)
When I create the Window/Control that contains your table, I also create an instance of MyViewModel and set its the Window.DataContext=myViewModelInstance
For your combobox binding use ItemsSource="{Binding Path=MyComboBoxItems, Mode=TwoWay}