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.
Related
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.
I am working on this problem for about a day now.
For some reason I am unable to TwoWay bind a value to a ComboBox if it is inside a ItemsControl. Outside works just fine.
I have an ObservableCollection of int? in my ViewModel:
private ObservableCollection<int?> _sorterExitsSettings = new ObservableCollection<int?>();
public ObservableCollection<int?> SorterExitsSettings
{
get { return _sorterExitsSettings; }
set
{
if (_sorterExitsSettings != value)
{
_sorterExitsSettings = value;
RaisePropertyChanged("SorterExitsSettings");
}
}
}
My XAML:
<ItemsControl ItemsSource="{Binding SorterExitsSettings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=ItemsControl}, Path=DataContext.ScanRouter.Stores}"
SelectedValue="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="name" SelectedValuePath="id" IsEditable="True" />
</DataTemplate>
</ItemsControl.ItemTemplate>
So the ComboBox is populated with a list of stores. It works fine so far.
The ObservableCollection SorterExitsSettings even has some values set which are shown in the displayed ComboBoxes. So setting the SelectedValue also works.
However when I change a selection, SorterExitsSettings wont change. While when I implement the ComboBoxes(100) without an ItemsControl it suddenly works fine.
<ComboBox ItemsSource="{Binding ScanRouter.Stores}" DisplayMemberPath="name" SelectedValuePath="id" IsEditable="True" SelectedValue="{Binding SorterExitsSettings[0], Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
Even better when I implement the ComboBoxes using the ItemsControl and also the example ComboBox shown above. When I change the single ComboBox's value it will change the value of the ComboBox inside the ItemsControl, but not the other way around.
Did somebody encounter this problem before?
My guess was that the ItemsControl doesn't like the fact that I am binding my selected value to an item in a list. However when I bind directly to a ViewModel property(Store) it also doesn't work.
I also tried using SelctedItem instead of SelectedValue and populate the ObservableCollection with Store objects instead of int?.
The problem is that you're binding your ComboBox's SelectedValue directly to the collection elements which are type int ?. This won't work, binding targets have to be properties. Try wrapping your int ? values in a class and expose the value as a property of that class with a getter and setter, i.e. something like this:
private ObservableCollection<Wrapper> _sorterExitsSettings = new ObservableCollection<Wrapper>();
... etc...
And:
public class Wrapper
{
public int? Value {get; set;}
}
And finally:
<ComboBox ... SelectedValue="{Binding Path=Value, Mode=TwoWay...
Post back here if you still have problems.
I have datagrid which consist of muliple data grid checkboxes,i want to get the datagrid checked items,i am able to get only the single selected row item,but i need collection of checked checkboxes, below code i am using .Please let me know how to resolve this
**Xaml*****
<DataGrid SelectedItem="{Binding SelectedRow, Mode=TwoWay}" ItemsSource="{Binding ManualDataTable}" Background="{Binding ElementName=gd,Path=Background}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding UserID}" Width="60" />
<DataGridTextColumn Binding="{Binding Name}" Width="140" Header="Name" FontSize="16" FontFamily="segoe_uilight" IsReadOnly="True" />
</DataGrid.Columns>
</DataGrid>
<Button BorderBrush="{x:Null}" Content="Add participants" Width="220" Height="50" FontSize="20" Command="{Binding SaveAssignedUser}"/>
*****View Model***********
DataTable _manualDataTable;
public DataTable ManualDataTable
{
get
{
return _manualDataTable;
}
set
{
_manualDataTable = value;
RaisePropertyChanged("ManualDataTable");
}
}
private List<DataRowView> selectedRow;
public List<DataRowView> SelectedRow
{
get
{
return selectedRow;
}
set
{
selectedRow = value;
RaisePropertyChanged(() => SelectedRow);
}
}
public void ExecuteSaveAssignedUser()
{
SelectedRow = new List<DataRowView>();**///need multiple checked checkboxes collection**
foreach (DataRowView drv in SelectedRow)
{
}
}
Your checkbox column is bound to a a property called UserID, which I guess is the name of a column in your DataTable. Whenever you check/uncheck a checkbox in the datagrid, the binding will change the value of UserID to true or false, in the relevant DataRow of the DataTable.
Why would you bind a checkbox column to a user ID? I suspect this isn't what you actually want. Instead you probably need to add a boolean column to your DataTable (e.g. IsSelected), and bind your checkbox column to that instead.
It looks like you are using MVVM, so it isn't possible to access the datagrid items from within your view-model. You can only access the data that the grid is bound to (i.e. your DataTable). Your view-model code needs to iterate through the rows in this DataTable, examining the value of the UserID column (or the "IsSelected" column if you add one!) to determine whether that row's checkbox is checked in the datagrid.
The SelectedItem property that you are binding to is completely unrelated to your checkbox column. Checking and unchecking these will have no effect on SelectedItem. This property is used to determine which row the user has selected with the mouse (which gives the row a different b/g colour). Forget about this property - it's not relevant to what you are trying to do.
As already mentioned elsewhere, try and avoid DataTables in WPF. Instead, define some kind of "User" class, and have your view-model expose a collection of these for your grid to bind to.
You seem to be missing the entire point of data binding... that is that you have access to all of the data that is displayed in your DataGrid from your code behind. You have bound the ManualDataTable DataTable property to the DataGrid, so the values that are data bound to the RadioButton controls are all in one column of your DataTable.
Therefore, all you need to do to access them is to look in your DataTable. There are several ways to achieve this, but here is one:
foreach(DataRow row in ManualDataTable.Rows)
{
if (row[requiredColumnIndex] == true) AddRowToSomeCollection(row);
}
However, if you're going to continue to use WPF, I'd seriously advise that you stop using these old classes, such as DataTables. Generally in WPF, we define custom classes and that makes everything much simpler in the long run.
I'm trying to set seletected value to a pre-loaded combobox using silverlight with MVVM.
I load these combobox items before selecting value.
For example I have a combobox to select a country. My first step is to load a List which is bound to the combobox source. This is loading perfectly.
After this, I have a "SelectedCountry" object bound to the selectedItem of the combobox in a two-way binding.
This is working perfect when I select any of the combobox values and my SelectedCountry object is correctly selected.
The problem comes when I try to assign the selected value in my ViewModel. This way, the combobox selecteditem is not updated.
I suppose this is because, on fact, they are not the same object (they have the same values but they are diferent references).
Should this work if I re-implement the equals method? Or should I find the same object by searching into the List?? This would be very easy because this two countries would be the same if they have the same id... but I can have more complex objects and I think the equals method would be better.
Thanks in advance!!
Edit for adding some code example:
<ComboBox Grid.Column="7" Margin="6,0" Name="cBTipoPoliza" VerticalAlignment="Center" TabIndex="4" ItemsSource="{Binding TiposPolizas, Mode=OneWay}" SelectedItem="{Binding TipoPoliza, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding nombre_tipo}" />
</DataTemplate>
</ComboBox.ItemTemplate>
Usually I just override the .Equals() method to check if they are equal by ID or Name
You should try to avoid having multiple copies of the same object in memory at the same time. One method to do this is to have your VM be the source of objects. When you load the list have the VM do it and expose an AvailableCountries ObservableCollection property on the VM that your ComboBox can bind to.
If your objects are semantically equal based on ID, definitely override Equals and == and != and hashcode. However be careful because if you're enabling people to update the objects you can run into collisions (even within the same instance of the app) where one screen is holding onto stale data.
I have a Silverlight control that has my root ViewModel object as it's data source. The ViewModel exposes a list of Cards as well as a SelectedCard property which is bound to a drop-down list at the top of the view. I then have a form of sorts at the bottom that displays the properties of the SelectedCard. My XAML appears as (reduced for simplicity):
<StackPanel Orientation="Vertical">
<ComboBox DisplayMemberPath="Name"
ItemsSource="{Binding Path=Cards}"
SelectedItem="{Binding Path=SelectedCard, Mode=TwoWay}"
/>
<TextBlock Text="{Binding Path=SelectedCard.Name}"
/>
<ListBox DisplayMemberPath="Name"
ItemsSource="{Binding Path=SelectedCard.PendingTransactions}"
/>
</StackPanel>
I would expect the TextBlock and ListBox to update whenever I select a new item in the ComboBox, but this is not the case. I'm sure it has to do with the fact that the TextBlock and ListBox are actually bound to properties of the SelectedCard so it is listening for property change notifications for the properties on that object. But, I would have thought that data-binding would be smart enough to recognize that the parent object in the binding expression had changed and update the entire binding.
It bears noting that the PendingTransactions property (bound to the ListBox) is lazy-loaded. So, the first time I select an item in the ComboBox, I do make the async call and load the list and the UI updates to display the information corresponding to the selected item. However, when I reselect an item, the UI doesn't change!
For example, if my original list contains three cards, I select the first card by default. Data-binding does attempt to access the PendingTransactions property on that Card object and updates the ListBox correctly. If I select the second card in the list, the same thing happens and I get the list of PendingTransactions for that card displayed. But, if I select the first card again, nothing changes in my UI! Setting a breakpoint, I am able to confirm that the SelectedCard property is being updated correctly.
How can I make this work???
If you are using Silverlight 3 you will need to use INotifyPropertyChanged.
Example:
public class CardViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Card> Cards { get; set; }
private Card _selectedCard;
public SelectedCard
{
get
{
return _selectedCard;
}
set
{
if (value != _selectedCard)
{
_selectedCard = value;
NotifyPropertyChanged("SelectedCard");
}
}
}
public CardViewModel()
{
Cards = new ObservableCollection<Card>();
//Populate Cards collection with objects
}
public void NotifyPropertyChanged(string item)
{
if (PropertyChanged!=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(item));
}
}
}
All you would need to do is set this class to your views DataContext and everything should be happy.
A pattern I've been using recently is to bind the data context of a container of detail info to the selected item of the list box. The XAML in your case becomes:
<StackPanel Orientation="Vertical">
<ComboBox x:Name="_lbxCards" <-- new
DisplayMemberPath="Name"
ItemsSource="{Binding Path=Cards}"
SelectedItem="{Binding Path=SelectedCard, Mode=TwoWay}"
/>
<StackPanel DataContext={Binding ElementName=_lbxCards,Path=SelectedItem}> <-- new
<TextBlock Text="{Binding Path=Name}" <-- updated
/>
<ListBox DisplayMemberPath="Name"
ItemsSource="{Binding Path=PendingTransactions}" <-- updated
/>
</StackPanel> <-- new
</StackPanel>
Turns out the problem isn't in the UI at all. The PendingTransactions class lazy-loads its values using a async WCF call to the server. The async pattern uses events to notify the caller that the operation is complete so the data can be parsed into the class. Because each Card has its own instance of the PendingTransactions class and we used a ServiceFactory to manage our WCF proxies, each instance was wiring up their event handler to the same event (we are using a singleton approach for performance reasons - for the time being). So, each instance received the event each time any of the instances triggered the async operation.
This means that the data-binding was working correctly. The PendingTransactions collections were overwriting themselves each time a new Card was viewed. So, it appeared that selecting a previous card did nothing when, in fact, it was selecting the correct object for binding, it was the data that was screwed up and make it look like nothing was changing.
Thanks for the advice and guidance nonetheless!