WPF ComboBox not displaying selected value - c#

I have managed to bind ItemsSource and ComboBox lets me choose each option, but I cannot see which option has been chosen. ComboBox is just blank.
XAML code:
<ComboBox
Name="Position"
Grid.Row="5"
SelectedValue="{Binding Position}"
ItemsSource="{Binding Positions}"
Style="{StaticResource MaterialDesignComboBox}"
Margin="15,10,15,10"
FontSize="12"/>
Tried basic ComboBox (non-material design) and results are identical.
I will provide more code if you need it, but so far it seems that this control is just broken, it doesn't work as it should. I'm probably missing some small detail how to properly set it up.
Edit
ViewModel:
public class WindowAddEmployeesViewModel : EmployeesViewModel, INotifyPropertyChanged
{
public ObservableCollection<PositionsViewModel> Positions { get; set; }
new public event PropertyChangedEventHandler PropertyChanged;
}
Base class contains things like FirstName, LastName, Position etc. INotifyPropertyChanged not implemented because Fody.PropertyChanged does it for me.
PositionViewModel:
public class PositionsViewModel : INotifyPropertyChanged
{
public string Position { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public override string ToString()
{
return $"{Position}";
}
}
Edit
Switching IsEditable to True makes it visible, but i don't want user to be able to edit it.

You misundestood the purpose of SelectedValue. You can bind to the SelectedValue instead of SelectedItem. It has nothing to do with the value being displayed by the ComboBox.
The displayed value can be defined by setting ItemsControl.DisplayMemberPath to the desired property on the data model, but only when ItemTemplate is not defined. DisplayMemberPath is meant to replace the DataTemplate in simple scenarios.
You obviously want to set the DisplayMemberPath.
Also your current binding
<ComboBox SelectedValue="{Binding Position}" .../>
won't resolve (no matter the state of ComboBox.IsEditable) as the DataContext of the ComboBox is obviously the WindowAddEmployeesViewModel and not the PositionsViewModel. This could've been a hint that you are using SelectedValue wrong.
SelectedItem: the currently selected data model.
SelectedValue: returns the property's value on the SelectedItem, defined by SelectedValuePath.
SelectedValuePath: sets the path to the property, which should be the SelectedValue on the SelectedItem. Argument is a string.
DisplayMemberPath: sets the path to a property on each data model which is used to display the item in the ComboBox. Argument is a string.
Data model
public class PositionsViewModel : INotifyPropertyChanged
{
public string Label { get; set; }
public string Position { get; set; }
public override string ToString() => Position;
}
The view
<!-- Since DisplayMemberPath="Position" the ComboBox will show the value of the Position property as its items -->
<ComboBox x:Name="PositionComboBox"
DisplayMemberPath="Position"
SelectedValuePath="Label"
ItemsSource="{Binding Positions}" />
<!--
Displays the PositionsViewModel. Implicitly invokes PositionsViewModel.ToString().
The TextBox will therefore display the property value of `PositionsViewModel.Position`.
-->
<TextBox Text="{Binding ElementName=PositionComboBox, Path=SelectedItem}" />
<!--
Displays the SelectedValue of the ComboBox. This value is defined by ComboBox.SelectedValuePath.
The TextBox will therefore display the property value of `PositionsViewModel.Label`
-->
<TextBox Text="{Binding ElementName=PositionComboBox, Path=SelectedValue}" />

Related

WPF combobox is empty when binding enum

I am trying to bind values of an enum to a combo box but the combo box remain empty with no options to choose.
This is the combo box xaml defintion:
<ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding Path=SkillItemSource}" SelectedItem="{Binding Path=neededSkill, Mode=TwoWay}" SelectedIndex="0" Margin="5" MinWidth="100"></ComboBox>
And this is the items source and selected item which are defined in the window's cs:
public Skill neededSkill = Skill.FirstSkill;
public string[] SkillItemSource
{
get
{
return Enum.GetNames(typeof(Skill));
}
}
What is missing for the values to appear in the combobox?
What is missing for the values to appear in the combobox?
You need to set the DataContext of the ComboBox, or a parent element, to an instance of the class where the SkillItemSource property is defined. If the property is defined in the code-behind, you could just set the DataContext to the view itself: this.DataContext = this;
Also, you can't mix types. If the ItemsSource is bound to an IEnumerable<string>, the SelectedItem property should be bound to a string property.
Also note that neededSkill must be defined as a public property for you to be able to bind to it.
Try this:
public Skill neededSkill { get; set; } = Skill.FirstSkill;
public IEnumerable<Skill> SkillItemSource { get; } = Enum.GetValues(typeof(Skill)).Cast<Skill>();

Extracting Data from DataGrid into Binded Combobox

What I am trying to do is take the string out of a specific column in my DataGrid and display that string in the Combobox.Text property. I am using this code to do so:
var dataString = ((DataRowView)dgvMain.SelectedItem).Row["Column"].ToString();
I have placed a breakpoint on this to see what it was pulling, and it was the correct String that is contained as an item in the Combobox, but whenever I try to set it via Combobox.Text, the Combobox is empty. I have however set my Combobox to isEditable = True and ReadOnly = True and this method works, but selecting an item in the Collection will display System.Data.DataRowView, due to the Combobox being binded to my DataTable. I'll add the XAML to my Combobox as well:
<ComboBox x:Name="cboAlarmType" HorizontalAlignment="Left" Margin="138,256,0,0" VerticalAlignment="Top" Width="320" TabIndex="5"
BorderBrush="Black" Background="White" ItemsSource="{Binding}" DisplayMemberPath="AlarmName" SelectedValuePath="AlarmName"
SelectedValue="{Binding Row.Column, ElementName=dgvMain}"/>
This method works in Textboxes and Checkboxes, but haven't seemed to figure it out for Comboboxes. Any guidance would be appreciated! :)
It appears to me that your DataContext for the ComboBox must be the DataTable you're using to populate the DataGrid, because ItemsSource="{Binding}". I'm going to assume that it's the DataContext for the DataGrid as well, and that your goal is to have the ComboBox selection reflect the selected row in the DataGrid.
<DataGrid
x:Name="dgvMain"
ItemsSource="{Binding}"
/>
<ComboBox
ItemsSource="{Binding}"
DisplayMemberPath="Column"
SelectedValuePath="Column"
SelectedValue="{Binding SelectedItem.Row[Column], ElementName=dgvMain}"
/>
If you'd like the selection changes to go both ways -- so a change to the ComboBox selection changes the DataGrid selection -- that's easy:
<DataGrid
x:Name="dgvMain"
ItemsSource="{Binding}"
SelectedValuePath="Column"
SelectedValue="{Binding SelectedValue, ElementName=ColumnComboBox}"
/>
<ComboBox
x:Name="ColumnComboBox"
ItemsSource="{Binding}"
DisplayMemberPath="Column"
SelectedValuePath="Column"
SelectedValue="{Binding SelectedItem.Row[Column], ElementName=dgvMain}"
/>
And here's a cleaner way to do the whole thing:
First, write a viewmodel class in C#.
public class ViewModelbase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class MyViewModel : ViewModelbase
{
public MyViewModel()
{
// I populated my DataTable with "Row 1", "Row 2", etc.
// Naturally you'd use a value from your own data.
SelectedColumnValue = "Row 2";
}
#region SelectedColumnValue Property
private String _selectedColumnValue = default(String);
public String SelectedColumnValue
{
get { return _selectedColumnValue; }
set
{
if (value != _selectedColumnValue)
{
_selectedColumnValue = value;
OnPropertyChanged();
}
}
}
#endregion SelectedColumnValue Property
public DataTable Data { get; protected set; }
}
Then use that SelectedColumnValue property to keep track of the selected value of "Column" for both controls. The bindings on the SelectedValue properties of the two controls will be two-way by default, because of framework stuff that makes it be that way (how's that for an explanation?). So if you change SelectedColumnValue programatically, the controls will update, and if the user changes the grid selection, the viewmodel property will be updated, which will in turn update the ComboBox -- and vice versa.
<DataGrid
ItemsSource="{Binding Data}"
SelectedValuePath="Column"
SelectedValue="{Binding SelectedColumnValue}"
/>
<ComboBox
ItemsSource="{Binding Data}"
DisplayMemberPath="Column"
SelectedValuePath="Column"
SelectedValue="{Binding SelectedColumnValue}"
/>
<ComboBox
ItemsSource="{Binding Data}"
DisplayMemberPath="ShoeSize"
SelectedValuePath="Column"
SelectedValue="{Binding SelectedColumnValue}"
/>
Another wrinkle is that DisplayMemberPath and SelectedValuePath needn't be the same. Say we've got a "ShoeSize" column in Data, and we'd like to have those values displayed in another ComboBox. So we can do that. We're still using Column as a unique identifier for rows in Data, but we can tell the ComboBox to display some other column.

Alternatives for bind an event to a property

I've a list box but if I click on an item, I must see the details of that item. I've made this code where I try to bind the SelectionChanged event to a property whit type RelayCommand and mode is two way.
<ListBox Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3"
SelectedItem="{Binding SelectedVillage, Mode=TwoWay}"
ItemContainerStyle="{StaticResource lstidflt}"
SelectionChanged="{Binding SelectedVillageChanged, Mode=TwoWay}"
ItemTemplate="{StaticResource weatheritemdt}"
ItemsSource="{Binding VillageList}" />
This doesn't work of course because you can't bind an event to a property or a property to a method and vice versa. You can only bind a property to a property. So the question is now are there alternatives to bind a SelectionChanged event to a property?
I use C# in a Windows universal 10 application with the MVVM light architecture.
You could just let the binding of the SelectedItem property
<ListBox ItemsSource="{Binding VillageList}" SelectedItem="{Binding SelectedVillage, Mode=TwoWay}" />
And do the job in the setter
public class VillageViewModel
{
public ObservableCollection<Village> VillageList { get; set; }
private Village selectedItem;
public Village SelectedItem
{
get { return selectedItem; }
set
{
if (selectedItem == value)
return;
selectedItem = value;
// Do logic on selection change.
}
}
}
What I do (in WPF) is bind the selected item to a full property then raise the event in the set part. It will look something like this;
private Village _SelectedVillage;
public Village SelectedVillage{
get {return _SelectedVillage;}
set {
_SelectedVillage = value;
RaiseEvent myEvent();
}
}
You can also raise the relaycommand or check for a trigger in xaml. If you go with the property, look at dependency property if it's available in win 10 universal.

Combobox with composite collection not updating with changes to observablecollection

As the title suggests; I have a combobox using a composite collection to bind to an observable collection using the MVVM pattern.
If I load my model with existing data then the combobox shows the values so I know the binding works. I can add items to the observable collection and they are shown in a data grid so I know the notify property changed events on the observable collection are working. I suspect it is not working because the composite collection is using a "Static Resource" as its source but if I change it to Dynamic Resource then I get the error:
A 'DynamicResourceExtension' cannot be set on the 'Source' property of
type 'Binding'. A 'DynamicResourceExtension' can only be set on a
DependencyProperty of a DependencyObject.
I have searched for days to find a solution and while others have faced similar problems the solutions have yet to solve my problem.
Here is my code for the model:
public class Model : ObservableObject
{
#region Properties
private string name;
public string Name
{
get { return this.name; }
set { this.name = value; }
}
private string balance;
public string Balance
{
get { return this.balance; }
set { this.balance = value; }
}
#endregion
My ViewModel:
public class ViewModel : ObservableObject
{
private ObservableCollection<Model> modelcollection;
public ObservableCollection<Model> ModelCollection
{
get { return modelcollection; }
set
{
modelcollection= value;
RaisePropertyChangedEvent("ModelCollection");
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChangedEvent("Name");
}
}
private string _balance;
public string Balance
{
get { return _balance; }
set
{
_balance = value;
RaisePropertyChangedEvent("Balance");
}
}
And finally the XAML of my view for the combobox:
<ComboBox MinWidth="100" SelectedValue="{Binding combovalue, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="Name">
<ComboBox.Resources>
<vm:ViewModel x:Key="CollectionKey"/>
</ComboBox.Resources>
<ComboBox.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding ModelCollection, Source={StaticResource CollectionKey}, UpdateSourceTrigger=PropertyChanged}"/>
</CompositeCollection>
</ComboBox.ItemsSource>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Thank you all so much in advance.
EDIT:
So I have moved in a direction; right or wrong is yet to be decided.
I think I have traced the problem to be outside the combobox itself so more context is needed here.
The comobobox is on the second tab of a tab control. Each tab item has its own data context pointing to its own view model. Data is entered on the first tab and I want that data to show up in the combobox on the second tab.
If I put a combobox on the first tab then that combobox updates with changes in the observable collection as entered on the first tab. So the issue, (I think), is that the combobox on the second tab is trying to bind to two different view models at the same time. One for the items source and a different one for the selected value.
There was a suggestion in another thread to use x:Reference for the data context but I can't seem to figure out the correct syntax for that.
If your still reading this then any help is really appreciated.
In the end I never got the combobox to populate from the other view model. I am still unsure if this is possible or not.
The solution that finally worked for me was to just combine everything I needed for binding into a single view model. It made the view model a bit heavy but cut down a lot on the XAML so I guess that's a win.

WPF call method when property changes

In C#, how can a method be called when a property changes (both method and property belong to the same class)?
e.g.,
class BrowserViewModel
{
#region Properties
public List<TreeViewModel> Status { get; private set; }
public string Conditions { get; private set; }
#endregion // Properties
// i'd like to call this method when Status gets updated
void updateConditions
{
/* Conditions = something depending on the TreeViewItem select status */
}
}
Binding
<TreeView Grid.Row="1"
x:Name="StatusTree"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"
ItemsSource="{Binding Path=Status, Mode=OneTime}"
ItemTemplate="{StaticResource CheckBoxItemTemplate}"
/>
Use-Case (if you are curious)
The property Status is bound to a TreeView control in the xaml. When it is updated, I'd like to call a method that updates the property Conditions. This property is bound to a TextBox in the xaml.
I'm new to Eventing in C#, so am a little lost.
Edit
class TreeViewModel implements INotifyPropertyChanged.
Conditions is updated by getting the IsChecked Value from the TreeView.
The size of the Status List never changes. When a TreeViewItem is selected/unselected the TreeViewModel changes.
TreeViewModel source (FooViewModel on this page)
Binding code above.
Didn't have to change Binding Mode for IsChecked.
<HierarchicalDataTemplate
x:Key="CheckBoxItemTemplate"
ItemsSource="{Binding Children, Mode=OneTime}"
>
<StackPanel Orientation="Horizontal">
<!-- These elements are bound to a TreeViewModel object. -->
<CheckBox
Focusable="False"
IsChecked="{Binding IsChecked}"
VerticalAlignment="Center"
/>
<ContentPresenter
Content="{Binding Name, Mode=OneTime}"
Margin="2,0"
/>
</StackPanel>
</HierarchicalDataTemplate>
I assume you want updateConditions to fire whenever an item is added/removed/changed in your list, not if the list reference itself changes.
Since you're implementing INotifyPropertyChanged within your TreeViewModel, I think you'll want to use ObservableCollection<T> instead of a plain List<T>. Check it here: http://msdn.microsoft.com/en-us/library/ms668604.aspx
Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
class BrowserViewModel
{
#region Properties
public ObservableCollection<TreeViewModel> Status { get; private set; }
public string Conditions { get; private set; }
#endregion // Properties
// i'd like to call this method when Status gets updated
void updateConditions
{
/* Conditions = something */
}
public BrowserViewModel()
{
Status = new ObservableCollection<TreeViewModel>();
Status.CollectionChanged += (e, v) => updateConditions();
}
}
CollectionChanged will fire whenever an item is added/removed/changed. As far as I know, it will consider it "changed" when its reference changes or any of its properties are changed (which is notified through INotifyPropertyChanged)
Just checked it here: http://msdn.microsoft.com/en-us/library/ms653375.aspx
ObservableCollection.CollectionChanged Event
Occurs when an item is added, removed, changed, moved, or the entire list is refreshed.
ObservableCollection<T> resides in the System.Collections.ObjectModel namespace, in System.dll assembly.

Categories