Issue with Binding for ComboBox in WPF - wpf

Below is the code
<ComboBox Name="cmbRegisteredDriveList"
Width="150" HorizontalAlignment="Center"
ItemsSource="{Binding Path=DriveList}"
SelectedItem="{Binding Path=SelectedDrive, Mode=TwoWay}"
IsEnabled="{Binding IsBusy, Converter={StaticResource NotConverter}}"
ItemContainerStyle="{StaticResource xxxx.ComboBoxItem.Style}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock x:Name="tbTemplate" Width="250" Visibility="Collapsed"/>
<TextBlock TextWrapping="NoWrap"
Text="{Binding VolumeLabel, Converter={StaticResource CenterEllipsisConverter}, ConverterParameter={x:Reference tbTemplate}}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
When ever we change the Volume Label for drive we are getting notification. But Selected item in combo box is not get refreshed. Can anyone help me on this. I want to display changed Volume Label for selected item in combo box.
public ObservableCollection<DiskDrive> _driveList;
public ObservableCollection<DiskDrive> DriveList { get { return _driveList; } }
private DiskDrive _selectedDrive;
public DiskDrive SelectedDrive
{
get { return _selectedDrive; }
set { _selectedDrive = value; NotifyPropertyChanged(() => SelectedDrive); } }
Also we are notifying it whenever it is needed.
NotifyPropertyChanged(() => DriveList);
NotifyPropertyChanged(() => SelectedDrive);
In class DiskDrive, the property VolumeLabel is defined like this:
/// <summary>
/// Get the volume name of this disk. This is the friendly name ("Stick").
/// </summary>
/// <remarks>
/// When this class is used to identify a removed USB device, the Volume
/// property is set to String.Empty.
/// </remarks>
private string _volumeLabel;
public string VolumeLabel
{
get { return _volumeLabel; }
set { _volumeLabel = string.IsNullOrWhiteSpace(value) ? string.Format(LocalizationManager.Instance["XXXX"], SerialNumber) : value; }
}

DiskDrive must implement INotifyPropertyChanged and VolumeLabel must raise the PropertyChanged event when changed. Otherwise the binding will not be updated.
Also, please notice that you will most probably leak memory when binding to a property of a class which does not implement INotifyPropertyChanged. See here.

Related

How to show the selected item of a WPF binding

C#:
public void SetCompetition(Window wT1)
{
//Add all the Copetition
wT1._competition = new List<Competition>();
wT1._competition.Add(new Competition { Logo = "3.png", Name = "test1", IsSelected = false });
wT1._competition.Add(new Competition { Logo = "3.png", Name = "test2", IsSelected = false });
wT1._competition.Add(new Competition { Logo = "3.png", Name = "test3", IsSelected = false });
wT1._competition.Add(new Competition { Logo = "3.png", Name = "test4", IsSelected = false });
wT1.cboSetupCompetition.ItemsSource = wT1._competition;
wT1.cboSetupCompetition.Items.Refresh();
}
Data Template:
<UserControl.Resources>
<System:Double x:Key="Double1">11</System:Double>
<DataTemplate x:Key="cmbCompetition">
<WrapPanel Height="30" >
<Label Content="{Binding Name}" ></Label>
</WrapPanel>
</DataTemplate>
</UserControl.Resources>
<ComboBox x:Name="cboSetupCompetition" ItemTemplate="{DynamicResource cmbCompetition}" HorizontalAlignment="Left" Margin="29,28,0,0" VerticalAlignment="Top" Width="173" RenderTransformOrigin="0.5,0.591" FontSize="12" Height="22" IsEditable="True" Background="#FFD8D8D8" SelectionChanged="UpdateCompetitionSelection"/>
I have a Combobox with a label and an image and when I select an item I would like to see the same format in the Combobox when it is closed. I am not getting any errors I am seeing the name of the application.Competition(this is my object Model) instead of the values of the image and label.
The SetCopetition is invoked when the application loads.
A TextBox is not able to display a Label and an Image or whatever elements that are in your DataTemplate in it.
Set the IsEditable property of the ComboBox to false and it should work as expected, i.e. your DataTemplate will be applied to the selected item when the ComboBox is closed:
<ComboBox x:Name="cboSetupCompetition" IsEditable="False" ItemTemplate="{DynamicResource cmbCompetition}" HorizontalAlignment="Left" Margin="29,28,0,0" VerticalAlignment="Top" Width="173" RenderTransformOrigin="0.5,0.591" FontSize="12" Height="22" Background="#FFD8D8D8" SelectionChanged="UpdateCompetitionSelection"/>
Your issue has nothing to do with MVVM...
the specific problem as Mn8 spotted is that IsEditable=true forces the combo to display a textbox as the selected item
However you are still thinking winforms not WPF, using code behind to link data into the view causes many problems and instability as quite often this breaks the binding connections which is what is suspected was your problem initially, using a proper MVVM approach will eliminate all these problems
the best overveiw of MVVM i know of is
https://msdn.microsoft.com/en-gb/library/hh848246.aspx
Model
this is your data layer, it handle storage and access to data, your model will handle access to files, databases, services, etc
a simple model would be
public class Model
{
public string Text { get; set; }
public Uri Uri { get; set; }
}
ViewModel
on top of your Model you have your View Model
this manages the interaction of your View with the model
for example here because it uses Prism's BindableBase the SetProperty method notifies the View of any changes to the data, the ObservableCollection automatically notifies of changes to the collection, it also uses Prism's DelegateCommand to allow method binding in the view
public class ViewModel:BindableBase
{
public ViewModel()
{
AddItem = new DelegateCommand(() => Collection.Add(new Model()
{
Text = NewText,
Uri = new Uri(NewUri)
}));
}
private string _NewText;
public string NewText
{
get { return _NewText; }
set { SetProperty(ref _NewText, value); }
}
private string _NewUri;
public string NewUri
{
get { return _NewUri; }
set { SetProperty(ref _NewUri, value); }
}
private Model _SelectedItem;
public Model SelectedItem
{
get { return _SelectedItem; }
set
{
if (SetProperty(ref _SelectedItem, value))
{
NewText = value?.Text;
NewUri = value?.Uri.ToString();
}
}
}
public ObservableCollection<Model> Collection { get; } = new ObservableCollection<Model>();
public DelegateCommand AddItem { get; set; }
}
View
the View ideally does nothing but displays and collects data, all formatting / Styling should be done here
firstly you need to define the data source, the usual way is via the data context as this auto inherits down the visual tree, in the example because i set the window's datacontext, i have also set it for everything in the window the only exception is the dataTempplate as this is set to the current item in the collection
i then bind properties to the datasource
Note the code behind file is only the default constructor no other code at all
<Window
x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<StackPanel>
<GroupBox Header="Text">
<TextBox Text="{Binding NewText}"/>
</GroupBox>
<GroupBox Header="URI">
<TextBox Text="{Binding NewUri}"/>
</GroupBox>
<Button Content="Add" Command="{Binding AddItem}"/>
<ComboBox ItemsSource="{Binding Collection}" SelectedItem="{Binding SelectedItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Uri}" />
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Window>

why is it displaying class name instead of property?

I have the following combobox in the xaml:
<ComboBox x:Name="cmbCharacters1" HorizontalAlignment="Left" Margin="18,21,0,0" VerticalAlignment="Top" Width="136" SelectedIndex="0" Height="32" RenderTransformOrigin="1.53,-1.281"
ItemsSource="{Binding CharacterEntity}" SelectedItem="{Binding Name}" SelectedValue="{Binding Tag}"/>
and the following class and binding code
public class CharacterEntity
{
public string Name { get; set; }
public string Tag { get; set; }
}
....
cmbCharacters1.ItemsSource = characters;//it is a List<CharacterEntity>
when I run it displays the class name instead of the content of Name property, what am I doing wrong?
I think you forgot to use this: DisplayMemberPath="Tag" Or "Name" whatever you wish to display!
You need to set the DisplayMemberPath in your ComboBox XAML.
This isn't a binding, since the ItemsSource is already bound - you just reference the field name, like so:
<ComboBox DisplayMemberPath="Name" ...
In the XAML you are setting the ItemsSource to a class CharacterEntity instead of List<CharacterEntity>, since you are setting the Itemssource in the code-behind remove it from the XAML and try. Also, you need to set DisplayMemberPath="Name" and set either SelectedItem or SelectedValue not both, if you are using SelectedValue then also use SelectedValuePath="Name"
<ComboBox x:Name="cmbCharacters1" SelectedItem="{Binding someCharacter}" DisplayMemberPath="Name" />
See also this answer: https://stackoverflow.com/a/3797074/424129
C#
public class CharacterEntity
{
public string Name { get; set; }
public string Tag { get; set; }
}
// Look up how to implement INotifyPropertyChanged, I didn't bother here
public class MyViewModel : INotifyPropertyChanged {
public MyViewModel(IEnumerable<CharacterEntity> chars)
{
CharacterEntities = new List<CharacterEntity>(chars);
}
private IEnumerable<CharacterEntity> _characterEntities;
public IEnumerable<CharacterEntity> CharacterEntities {
get { return _characterEntities; }
set { _characterEntities = value;
OnPropertyChanged("CharacterEntities"); }
}
private CharacterEntity _characterEntity
public CharacterEntity SelectedCharacterEntity
}
XAML
ItemsSource is the source for the items. Your binding made no sense. You want to give it a list of CharacterEntity, but you bind to the CharacterEntity class? What list are you talking about? And don't set it in code behind. XAML makes much more sense if you use a viewmodel.
Now, somehow the above MyViewModel class needs to be made the DataContext of some control that owns the ComboBox.
<ComboBox HorizontalAlignment="Left" Margin="18,21,0,0"
VerticalAlignment="Top" Width="136" SelectedIndex="0" Height="32"
RenderTransformOrigin="1.53,-1.281"
ItemsSource="{Binding CharacterEntities}"
SelectedItem="{Binding SelectedCharacterEntity}"
DisplayMemberPath="Name"
/>
When you have it like this:
SelectedItem="{Binding Path=Name}"
it will use what ever is now selected in combobox, that class property of Name is being used as Selected. Without Path, you are binding to that combobox Name object. BUT anyways, this shouldn't yet work in your case with Path. So to have it work as you want it to, try this:
Have a SelectedItem binded to CharacterEntity class:
SelectedItem="{Binding SelectedEntity}" // Class instance of CharacterEntity
And then you have a Text binded to that selected entity class property of Name:
Text="{Binding Path=Name}" // Binded to property of Name
SelectedValue="{Binding Path=Tag}" // Binded to property of Tag
This way it should work. You should have a combobox binded to viewmodel and that viewmodel should have a property(class instance of CharacterEntity) of SelectedEntity. Hopefully this helps:
public class CharacterViewModel
{
public CharacterEntity SelectedEntity {get;set;}
public List<CharacterEntity> characters {get;set;} // use ObservableCollection insteand of List(Automatically update UI if list changes)
}
And XAML:
<ComboBox x:Name="cmbCharacters1" HorizontalAlignment="Left" Margin="18,21,0,0" VerticalAlignment="Top" Width="136" SelectedIndex="0" Height="32" RenderTransformOrigin="1.53,-1.281"
ItemsSource="{Binding characters}" Text="{Binding Path=Name}" SelectedItem="{Binding SelectedEntity}" SelectedValue="{Binding Path=Tag}"/>
Also has in codebehind e.g in constructor:
CharacterViewModel charViewModel = new CharacterViewModel();
cmdCharacters1.DataContext = charViewModel;
cmdCharacters1.ItemsSource= charViewModel.characters;
I'm terrible at explaining this, but I hope it makes sense with my code.

Caliburn.Micro and TabControl

I'm trying to bind a ObservableCollection of viewmodels to a TabControl.
In my MainViewModel I have : (INotifyPropertyChanged is handled through Fody.PropertyChanged)
public ObservableCollection<ImageManagementViewModel> ImageManagement { get; set; }
And my MainView :
<TabControl
Margin="5"
x:Name="ImageManagement" />
My ImageManagementViewModel implements IHaveDisplayName, as the documentation seems to indicate is needed.
If the TabControl’s DisplayMemberPath is not set and the ViewModel implements IHaveDisplayName, then we set it’s ItemTemplate to the DefaultHeaderTemplate, which looks like this: [...]
/// <summary>
/// Gets the display name
/// </summary>
public string DisplayName
{
get
{
return string.Format("{0}x{1}", this.X, this.Y);
}
set
{
throw new NotImplementedException();
}
}
But my tab's titles are always "TestApplication.ViewModels.ImageManagementViewModel", instead of the DisplayName.
If I implement the DisplayName "by hand" it works, but I'd like it to be automatic, because why doesn't it work ?
<TabControl
Margin="5"
x:Name="ImageManagement">
<TabControl.ItemTemplate>
<ItemContainerTemplate>
<TextBlock Text="{Binding DisplayName}" />
</ItemContainerTemplate>
</TabControl.ItemTemplate>
</TabControl>
What am I missing ?

EventToCommand SelectionChanged in Combobox to pass parameter

I have a combobox with rules in a view and it works well, but I want another field of the model from the itemsource to be used to bind to (or as I used it) update another field score.
E.g. if you select rule 1 in the combobox it should update the score field in the view with 1 and if you change the selecteditem to rule 2 it should show 2 in the score field.
My code might be a bit crippled because I experimented 'on the way here' to achieve the desired result, I have a ScoreView with a datagrid which itemsource is scores:
SelectionChangedCommand = new RelayCommand<string>((_score) => SelectedRuleChanged(_score));
private void SelectedRuleChanged(string _score)
{
int _tmpint;
_tmpint = this.SelectedScore.Model.score;
int.TryParse(_score, out _tmpint);
this.SelectedScore.Model.score = _tmpint;
//Todo weghalen lokale rules collectie en get_rules voids
//get_rules_by_ruleset(this.SelectedMatch.Model.ruleset);
}
<ComboBox
Height="23" HorizontalAlignment="Left"
Name="cmbRules" VerticalAlignment="Top" Width="100" ItemsSource="{Binding MatchVM.Rules}" SelectedValue="{Binding Model.ruleid, Mode=TwoWay}"
DisplayMemberPath="Model.name" SelectedValuePath="Model.ruleid">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding MainScore.SelectionChangedCommand, Mode=OneWay,
Source={StaticResource Locator}}" CommandParameter="{Binding Model.score}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
I try to pass the elements selecteditem (which I bound to model.score) as a command parameter..
Maybe I should use the selecteditem of MatchVMRule but the score text field is bound to the Scores ViewModel instead of the Rules ViewModel?
Thanks in advance,
Mike
UPDATE SOLVED
I finally solved it by creating two separate props rules collection and selected rule in my MainScoreViewModel.
I got rid of the eventocommand and handled the update of the score field in my Score Model based on the score of the Rule Model in the setter of the SelectedRule:
/// <summary>
/// Gets the SelectedRule property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public RuleViewModel SelectedRule
{
get
{
return _selectedRule;
}
set
{
if (_selectedRule == value)
{
return;
}
_selectedRule = value;
this.SelectedScore.Model.score = this.SelectedRule.Model.score;
RaisePropertyChanged(SelectedRulePropertyName);
}
}
<ComboBox
Height="23" HorizontalAlignment="Left"
Name="cmbRules" VerticalAlignment="Top" Width="100" ItemsSource="{Binding ScoreVM.Rules,
Mode=TwoWay}" SelectedValue="{Binding Model.ruleid, Mode=TwoWay}"
DisplayMemberPath="Model.name" SelectedValuePath="Model.ruleid" SelectedItem="{Binding
ScoreVM.SelectedRule, Mode=TwoWay}">
</ComboBox>
What about using the PropertyChange notification on your ViewModel to handle the SelectionChanged event instead of trying to handle it from the View?
public ParentViewModel()
{
this.Model.PropertyChanged += Model_PropertyChanged;
}
void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "ruleid":
this.SelectedScore.Model.Score = Model.Score;
}
}

Siliverlight databound combobox doesn't display initialized value

I am databinding a view to a viewmodel and am having trouble initializing a combobox to a default value. A simplification of the class I'm using in the binding is
public class LanguageDetails
{
public string Code { get; set; }
public string Name { get; set; }
public string EnglishName { get; set; }
public string DisplayName
{
get
{
if (this.Name == this.EnglishName)
{
return this.Name;
}
return String.Format("{0} ({1})", this.Name, this.EnglishName);
}
}
}
The combobox is declared in the view's XAML as
<ComboBox x:Name="LanguageSelector" Grid.Row="0" Grid.Column="1"
SelectedItem="{Binding SelectedLanguage,Mode=TwoWay}"
ItemsSource="{Binding AvailableLanguages}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
and the viewmodel contains this code
private List<LanguageDetails> _availableLanguages;
private LanguageDetails _selectedLanguage;
public LoginViewModel()
{
_availableLanguages = LanguageManager.GetLanguageDetailsForSet(BaseApp.AppLanguageSetID);
_selectedLanguage = _availableLanguages.SingleOrDefault(l => l.Code == "en");
}
public LanguageDetails SelectedLanguage
{
get { return _selectedLanguage; }
set
{
_selectedLanguage = value;
OnPropertyChanged("SelectedLanguage");
}
}
public List<LanguageDetails> AvailableLanguages
{
get { return _availableLanguages; }
set
{
_availableLanguages = value;
OnPropertyChanged("AvailableLanguages");
}
}
At the end of the constructor both _availableLanguages and _selectedLanguage variables are set as expected, the combobox's pulldown list contains all items in _availableLanguages but the selected value is not displayed in the combobox. Selecting an item from the pulldown correctly displays it and sets the SelectedLanguage property in the viewmodel. A breakpoint in the setter reveals that _selectedLanguage still contains what it was initialized to until it is overwritten with value.
I suspect that there is some little thing I'm missing, but after trying various things and much googling I'm still stumped. I could achieve the desired result in other ways but really want to get a handle on the proper use of databinding.
You need to change the order of you bindings in XAML so that your ItemsSource binds before the SelectedItem.
<ComboBox x:Name="LanguageSelector" Width="100"
ItemsSource="{Binding AvailableLanguages}"
SelectedItem="{Binding SelectedLanguage,Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
If you set a breakpoint on the 'get' of both the SeletedLanguage and AvailibleLanguage, you will notice that the SelectedLanguage gets hit before your AvailibleLanguage. Since that's happening, it's unable to set the SelectedLanguage because the ItemsSource is not yet populated. Changing the order of the bindings in your XAML will make the AvailibleLanguages get hit first, then the SelectedLanguage. This should solve your problem.
1) When you assign the SelectedLanguage, use the public property SelectedLanguage instead of the private _selectedLanguage, so that the setter gets executed,
2) You need to move the assignment of the selectedlanguage to the moment that the view has been loaded. You can do it by implementing the Loaded event handler on the View. If you want to be "mvvm compliant" then you should use a Blend behavior that will map UI loaded event to a viewmodel command implementation in which you would set the selected language.

Resources