Binding multiple ComboBoxes to the same ItemsSource causes problems - c#

I have 2 ComboBoxes whose ItemsSource is bound to the same ObservableCollection:
public ObservableCollection<MyType> MyTypeCollection { get; set; }
I have defined 2 different SelectedItem properties which in turn are bound to the correct ComboBox.
public MyType MySelectedItem1 { get; set; }
public MyType MySelectedItem2 { get; set; }
Whenever I select an item in one of the ComboBoxes, both SelectedItem properties defined in my class are set to that selection. Both ComboBoxes are also set visually to that value.
Why is this?
I tried several things like Mode=OneWay etc, but it's not working.
Here's my XAML (trimmed down for the question):
<ComboBox SelectedItem="{Binding MySelectedItem1}"
ItemsSource="{Binding MyTypeCollection, Mode=OneWay}"/>
<ComboBox SelectedItem="{Binding MySelectedItem2}"
ItemsSource="{Binding MyTypeCollection, Mode=OneWay}"/>

I'm guessing you have IsSynchronizedWithCurrentItem="True" on both ComboBox controls. Remove it from both, and that should solve your problem.
Setting IsSynchronizedWithCurrentItem="True" tells the ComboBox that it should keep its SelectedItem in sync with the CurrentItem in the underlying ICollectionView. Since you are binding directly to a collection, and not a collection view, both combo boxes are using the default collection view, which is a common instance shared across controls. When one of your combo boxes updates the collection view's selection, the other sees the change and updates its own selection to match it.

Related

Can not change the property of an object after initial binding

I got stuck on a problem. In the project I am working on I have to populate one column of ListView with cheboxes and another one with comboboxes. The following is the data model that I am using as an ObservableCollection to bind it to a listview. Works really well!
public class PointDataMainListView
{
public string CheckBoxName { get; set; }
public ObservableCollection<string> ComboBoxItems{ get; set; }
public Visibility visibility { get; set; }
}
Except I have a hard time changing the properties of the combobox itself. In particular the visibility property.
The following is the data template i am using for the combo box
<DataTemplate x:Key="ComboBoxCell">
<ComboBox x:Name="ComboBox"
ItemsSource="{Binding ComboBoxItems}"
Width="100"
Visibility="{Binding visibility}"/>
</DataTemplate>
When populating the listview for the first time or adding a new item to a listview visibility could be set no problem. When visibility inside my ObservableCollection < PointDataMainListView > is changed for the item already displayed nothing is happening.
One of the solution I was looking into is trying to itterate through a list view items to try and get a reference to the actual combobox to change it's property. That said, I believe there must be a more elegant solution to achieve the desired results. Thank you for any help.
Your class needs to implement INotifyPropertyChanged and your properties setters need to invoke the PropertyChanged method.

Binding from one ListBox to another ListBox?

I am trying to bind a ListBox to another ListBox within the same window. The left hand sided Listbox has data in it that one can select. But I want a user to be able to click on the item(s) in the left hand listbox and those same item(s) would be displayed in the other listbox on the right hand side.
EDITED: Of course you can bind a UI property to another UI property (Dependency Property actually) using ElementName, but I recommend to bind the properties to one view model. See a simplified example below.
View model:
public ObservableCollection<ItemObject> Items { get; set; }
public ObservableCollection<ItemObject> SelectedItems { get; set; }
Left:
<ListBox ItemsSource="{Binding Items}" SelectedItems="{Binding SelectedItems}" />
(Note that there is no SelectedItems dependency property actually. See question like: Select multiple items from a DataGrid in an MVVM WPF project)
Right:
<ListBox ItemsSource="{Binding SelectedItems}" />
This works fine. Furthermore, with this approach, the list on the right hand can be customized with ease (eg order, filter, ... by using CollectionView).
private ICollectionView _collectionView;
private ICollectionView _CollectionView {
get { return _collectionView
?? (_collectionView = CollectionViewSource.GetDefaultView(SelectedItems)); }
}
public ICollectionView FilteredItems {
get { _CollecitionView.Filter(...); }
}
<ListBox ItemsSource={"Binding FilteredSelectedItems"} />
Such an MVVM approach is sometimes laborious, but eventually found as beneficial.
You name the first listbox, then any other control on the xaml will bind to that control using it's name in the ElementName attribute of the binding.
For example there are two listboxes and one text box. The top listbox has multiselections and those selection(s) are shown on the lower listbox. While the textbox only gets the first item selected.
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<converters:PathToFilenameConverter x:Key="FilenameConverter" />
<x:Array x:Key="FileNames" Type="system:String">
<system:String>C:\Temp\Alpha.txt</system:String>
<system:String>C:\Temp\Beta.txt</system:String>
<system:String>C:\Temp\Gamma.txt</system:String>
</x:Array>
</StackPanel.Resources>
<ListBox Name="lbFiles"
SelectionMode="Multiple"
ItemsSource="{StaticResource FileNames}"
Margin="10"/>
<ListBox ItemsSource="{Binding SelectedItems, ElementName=lbFiles }" Margin="10" />
<TextBlock Text="{Binding SelectedItem,
ElementName=lbFiles,
Converter={StaticResource FilenameConverter}}"
Margin="10" />
</StackPanel>
Note...the code is binding using the SelectedItems property for the lower list box and not SelectedItem used by the TextBlock.
As an aside, another answer has the use of an ObservableCollection, that is not needed unless the array is dynamically changing; otherwise any array can be used. Depending on loading, say from a VM, it may need to adheres to the INotifyPropertyChanged.

Two-way binding combobox to enum

I have a View that is linked to my ViewModel using a DataTemplate, like this
<DataTemplate DataType="{x:Type ViewModels:ViewModel}">
<Views:View />
</DataTemplate>
The ViewModel holds a property ProcessOption that is of type MyEnum?, where MyEnum is a custom enumeration that has let's say 3 values: Single, Multiple and All. I am trying to bind a combobox to this property, so the approach I am following is:
ViewModel has a property of List<string> that is
public List<string> Options
{
get
{
return Enum.GetNames(typeof(MyEnum)).ToList();
}
}
to which I bind the ItemsSource property of the Combobox. Then, in addition to the ProcessOption property, the ViewModel also has an OptionName property (of string), which is intended to hold the selected option's name. The ViewModel implements INotifyPropertyChanged and both properties raise the PropertyChanged event in their setters. The binding I am using then is:
<ComboBox ItemsSource="{Binding Options}"
SelectedItem="{Binding OptionName}"
SelectedValue="{Binding ProcessOption}"/>
This works fine up to this point. Initially the combobox is empty and both properties are null, and when the user selects an option this is propagated to the ViewModel as it should.
The problem appears when I load the data from a Database, and I want to load the controls with initial values. In this case, in the ViewModel's constructor I have this:
this.ProcessOption = objectFromDB.ProcessOption // this is the value restored from DB, let's say it is MyEnum.Multiple
this.OptionName = Options.First(x => x.Equals(Enum.GetName(typeof(MyEnum), objectFromDB.ProcessOption)));
The problem is, although the above sets the two properties to their correct values, they are later set to null from the Combobox binding, so the initial values are not kept. I have also tried to do something like if (value == null) { return; } in the properties' setters, in which case they have the correct values after the View loads, however the Combobox still does not display the correct option, it is empty.
I should also note that I've also tried setting IsSynchronisedWithCurrentItem and it doesn't make any difference, apart from the fact that the first element is displayed instead of the combobox being empty.
Can anyone help with this binding? Any help will be very much appreciated, this is driving me crazy!
<ComboBox ItemsSource="{Binding Options}"
SelectedItem="{Binding OptionName}"
SelectedValue="{Binding ProcessOption}"/>
Your binding doesn't look like it should work at all -- you don't have TwoWay binding set up, and I think SelectedItem and SelectedValue is an either/or proposition.
I suggest that you get rid of OptionName and just bind SelectedItem to ProcessOption (TwoWay) with an IValueConverter that will convert to/from string.

How to extract data from datagrid in wpf?

I have difficulty in extracting data from datagrid. Since I was using selectionchange event on datagrid. I managed to get the selected row and column index. But I couldn't find any properties to get the exact data from the datagrid or datagridrow or selectedcells. Hope to get some advice from you guys, cheers.
Bind your DataGrid to an ItemsSource containing your data, and then your SelectedItem will be the item in your ItemsSource.
You had a comment above of using ComboBoxes, so here's an example:
<DataGrid ItemsSource="{Binding MyData}" AutoGernateColumns="False">
<DataGrid.Columns>
<DataGridComboBoxColumn ItemsSource="{Binding Path=DataContext.ComboBoxOptions, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValueBinding="OptionId" />
</DataGrid.Columns>
</DataGrid>
The DataContext for your DataGrid would contain
ObservableCollection<SomeItem> MyData { get; set; }
ObservableCollection<Option> ComboBoxOptions{ get; set; }
The SomeItem class would have a property of OptionId, and the Option class would have
an Id and Name field.
In this example, when you select an item in the DataGrid the DataGrid.SelectedItem would contain the SomeItem
Remember, in WPF your Data (DataContext) is your application, and your UI elements like ComboBoxes, TextBoxes, DataGrids, etc are all just a pretty interface to let the user interact with your Data
In your MouseDoubleClick Event , use the dataGrid1.SelectedIteam as Object.
The code should be like somewhat like following :
private void dataGrid1_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
edit(this.dataGrid1.SelectedItem as YourObject);
}

Silverlight Combobox loses visual value, but keeps selected value when the page it's on is hidden and reshown

I'm using a Silverlight 4.0 project that utilizes MVVM and we have a combobox that resides on a view and has its values and selected value bound to an observable collection of Organizations and a SelectedOrganization respectively (both values exist on the viewmodel). With our project the page that this control is on can be hidden or shown. The first load everything looks great but when you go to a different control (hide the tab with the control and then go back to it) the value that is currently selected in the combo box looks like it's blank, but when I debug, the selected value is still there.
The visual tree is getting recreated, but I have no idea why the combobox loses the text that should be in the box when the parent page is hidden and then re-shown. All other controls on the page behave correctly (autocompletetextbox, textblocks, textboxes, all of which have data bound to the viewmodel the same way).
Here's how the combobox is declared:
<ComboBox
SelectedItem="{Binding SelectedOrganization, Mode=TwoWay}"
ItemsSource="{Binding Organizations}"
DisplayMemberPath="Name"
Margin="5,0"
MinWidth="100" />
the Class for the organization is here:
[DataContract]
public class Organization
{
[DataMember]
public Guid OrganizationID { get; set; }
[DataMember]
public string Name { get; set; }
}
and the viewmodel has the following code for the bindings:
public Organization SelectedOrganization
{
get { return (Organization)GetValue("SelectedOrganization"); }
set
{
SetValue("SelectedOrganization", value);
}
}
public ObservableCollection<Organization> Organizations
{
get { return (ObservableCollection<Organization>)GetValue("Organizations"); }
set { SetValue("Organizations", value); }
}
What do I need to do to keep the selected value when I switch parent pages?
The problem is that I declared the ItemsSource AFTER the SelectedItem. Apparently this is a bug in Silverlight 3 and 4. The answer was discussed here Silverlight Combobox and SelectedItem.
Just a quick note on yet a SL3 bug.
I haven't reproduced this bug in a clean environment (since I'm getting quite tiered of reproducing SL ComboBox bug's...), but I experienced an issue with roughly this setup:
ItemsSource binds to property of type List on Object X.
SelectedItem binds to property of type String on Object X.
Object X implements INotifyPropertyChanged.
SelectedItem is set after ItemsSource in XAML code as the above post instructs.
ItemsSource is set to TwoWay BindingMode.
Behaviour: When the user TAB's from a textbox into the combobox, the combobox value is 'blanked', while the ViewModel maintains its value. The value is displayed again correctly when the user TAB's out of the ComboBox. Please note that the value is not blanked, if the combobox is merely clicked, or if it tabbed to from another combobox.
Solution: When stepping through the code with a debug'er it seems that SelectedItem is returned before ItemsSource, even though ItemsSource is declared before SelectedItem in the XAML code.
The solution was to change the ItemsSource from TwoWay BindingMode to OneWay BindingMode.
Probably this prevents some events from being fiered behind the scenes.
Br. Morten
Could be helpful if adding to previous post, I noticed that my selectedItem binding property must contain a conditional that avoid assing null value, because combobox control still wants to reset value once the control is hidden by scroll or whatever.
i.e. :
public string Month{
get {return _month;}
set {
if (value==null)
return;
_month = value;
}
}
Use this
<ScrollViewer Grid.Row="6" Grid.ColumnSpan="4" Height="190">
<sdk:DataGrid Name="datagridInvestigation"
AutoGenerateColumns="False" Width="650"
MinHeight="180" >
</sdk:DataGrid>
</ScrollViewer>
instead of
<sdk:DataGrid Name="datagridInvestigation"
AutoGenerateColumns="False" Width="650"
Height="180" ScrollViewer.HorizontalScrollBarVisibility="Auto" >

Categories

Resources