I have seen and tested a lot of material about this and I have the problem again. I have a collection of cities bound to the combobox and I want to set the selected item (selected city) when I click the button (for simplicity)
this is my combobox in xaml:
<ComboBox Grid.Column="1"
HorizontalAlignment="Left"
Margin="65,10,0,0"
Height="25"
Name="Cities"
DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True"
VerticalAlignment="Top"
Width="71">
</ComboBox>
this is code in corresponding ViewModel:
private City _selectedCity;
public City SelectedCity
{
get { return _selectedCity; }
set
{
_selectedCity = value;
NotifyOfPropertyChange(() => SelectedCity);
MessageBox.Show(SelectedCity.Name);
}
}
When I click the button this line of code is executed:
SelectedCity = CitySecond;
where, CitySecond is one random item from the Cities Collection.
When I click this button the desired city name appears in MessageBox, but nothing changes in combobox. I tested it in Debug mode and everything looks fine, but as it seems comboBox doesn't feels that something has changed.
I have also added the following line of code in combobox xaml definition but nothing changed:
SelectedItem="{Binding SelectedCity}"
What Can I do?
EDIT:
I have created another project, where everything works fine, but here is mystery. Does anything other affects the combobox to work properly?
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 have a listview that when selected, will populate data from the selected lineitem into separate textboxes.
I used databinding to accomplish the task, which seems to work fine:
<TextBox x:Name="SKU_TxtBox" HorizontalAlignment="Left" Height="23" Margin="10,21,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" Text="{Binding SelectedItem.SKU, ElementName=Inventory_ListView, Mode=OneWay}" />
The above code works correctly. The problem starts if in the codebehind I have to change the textbox value; afterwards the databinding stops.
SKU_TxtBox.text = ""
After the above line runs, the textbox will remain blank no matter what is selected in the listview.
When working with bindings, you should always manipulate only the binding source.
Inventory_ListView.SelectedItem.SKU = ""
Or a better approach is to have a View Model bound to the view. in which you define a Dependency Property (currentSKU). Then bind it to both Inventory_ListView.SelectedItem and SKU_TxtBox.text. Then it will be:
CurrentSKU = ""
I am working with a WPF view with Prism.MVVM which allows our users to edit records.
Originally the record to be edited was selected via ComboBox.
<ComboBox IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Records}"
SelectedItem="{Binding SelectedRecord}"/>
This worked, but users wanted a more efficient way of finding which records had fields which needed updating so we have added a read only DataGrid which they can sort and visually spot which records they are interested in. Next they want to select the record to edit off the grid (but keep the combo box). This is where things go wrong.
Ideally the behavior we are looking for is:
If user selects a record from combo box:
The selected record is loaded in the form
The selected record is shown as selected in the combo box.
The selected record is shown as selected in the grid.
If user selects a record in Grid
single click to select record.
The selected record is loaded in the form
The selected record is shown as selected in the combo box
The selected record is shown as selected in the grid.
Most Successful Attempt
Trigger Command on SelectionChanged event of DataGrid
<DataGrid x:Name="TheDataGrid"
ItemsSource="{Binding Source={StaticResource GridRecords}}"
SelectedItem="DataContext.SelectedRecord, ElementName=LayoutRoot, Mode=OneWay}">
...
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction CommandParameter="{Binding SelectedItem, ElementName=TheDataGrid}"
Command="{Binding DataContext.SelectRecordFromGridCommand, ElementName=LayoutRoot}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
DelegateCommand:
Public ReadOnly Property SelectRecordFromGridCommand As DelegateCommand(Of TheRecordType) = new DelegateCommand(Of TheRecordType)(Sub(r) SelectedRecord = r)
This was attempted with various options for the SelectedItem binding mode.
If the DataGrid SelectedItem binding is removed, We get 1,2,4,5,6, and 7. but selecting the record from the combo box would not show the record as selected in the grid.
If the DataGrid SelectedItem binding is set to OneWay, Selecting a record via the combo box breaks: Setting SelectedRecord triggers the SelectionChanged event in the DataGrid, which uses the value before the event and effectively sets everything back to the original value.
This can be remedied by introducing a sentinal on the Set of the Property in the ViewModel
Private _selectedRecord As TheRecordType
Private _enableRecordSelection As Boolean = true
Public Property SelectedRecord As TheRecordType
Get
Return _selectedRecord
End Get
Set(value As TheRecordType)
If _enableRecordSelection
_enableRecordSelection = false
SetProperty(_selectedRecord , value)
_enableRecordSelection = true
End If
End Set
End Property
This actually works, and we came up with it while writing the question, but feels horribly hacky. My gut is telling me there has to be a better way so I'm still asking:
Is there a clean (preferably xaml only) way to set this up?
The other most successful things we tried:
Straight xaml configuration for the DataGrid with TwoWay binding
<DataGrid x:Name="TheDataGrid"
ItemsSource="{Binding Source={StaticResource GridRecords}}"
SelectedItem="DataContext.SelectedRecord, ElementName=LayoutRoot, Mode=TwoWay}"/>
With this, we satisfy requirements 1 through 6; however when selecting the record through the grid, the previous record is always highlighted instead of the current one.
DataGrid.InputBindings
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftClick"
CommandParameter="{Binding SelectedItem, ElementName=TheDataGrid}"
Command="{Binding DataContext.SelectRecordFromGridCommand, ElementName=LayoutRoot}"/>
</DataGrid.InputBindings>
With no SelectedItem binding, this behaves similarly to the no binding InteractionTrigger on SelectionChanged, except it requires the user to perform multiple mouse actions. A first click selects the row in the grid (actual bold blue selection) The second click triggers the Command.
With a OneWay binding on SelectedItem, this behaves similarly to the straight xaml config, again except needing to click multiple times.
Again to reiterate the question:
Is there a cleaner way to accomplish the 7 requirements than to resort to the sentinal value on the property setter?
According to you ask, I understand that you want to sync the selected item in Datagrid and ComboBox. If I were you, I will use the that two control binding the same object(SelectedRecord). I only familiar with C#, so the code is write in C#. Hope it can help you.
For XAML:
<DataGrid ItemsSource="{Binding Records}"
SelectedValue="{Binding SelectedRecord}" />
<ComboBox Grid.Column="1" ItemsSource="{Binding Records}"
DisplayMemberPath="Id"
SelectedValue="{Binding SelectedRecord}" />
For ViewModel:
public ObservableCollection<Record> Records { get; } = new ObservableCollection<Record>();
public Record SelectedRecord
{
get { return _selectedRecord; }
set
{
_selectedRecord = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class Record
{
private static int id = 0;
public Record()
{
Id = ++id;
}
public int Id { get; set; }
}
I have a WPF user control (FileSelectionView.xaml) with a combo box that displays data. My WPF looks like:
<ComboBox Width="250"
HorizontalAlignment="Left"
ItemsSource="{Binding Path=FileTypes}"
SelectedItem="{Binding Path=FileType, Mode=TwoWay}" />
In my View Model file (FileSelectionViewModel.cs), I have a List that binds to this control that successfully works. The data looks like:
<Please select a file>
File Type 1
File Type 2
I have tried to set the SelectedIndex property to 0 so that "<Please select a file>" shows up when the user control renders, but it is not working. It doesn't show anything, but when I click on the combo box, I do see all my items.
Is there something I'm missing?
Instead of using SelectedIndex, After updating the ItemsSource, update the selected item with the following code from viewmodel
FileType = "Please select a value";
IT works just fine, if you do it in XAML, I don't see it in your XAML, did you forget?
<ComboBox Width="250"
HorizontalAlignment="Left"
ItemsSource="{Binding Path=FileTypes}"
SelectedItem="{Binding Path=FileType, Mode=TwoWay}"
SelectedIndex="0"/>
Note that only will work initially, then you'll have to reset it again when you need it.. via trigger, or code behind.
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.