So I have a custom class for my object like this:
class ClassA
{
public ClassA(string name)
{
this.AName = name;
}
public string AName { get; set; }
}
Then, I have a list of these in my ViewModel:
public List<ClassA> ObjectList
{
get { return _myobjects; }
set
{
_myobjects = value;
NotifyPropertyChanged("ObjectList");
}
}
Now, I have a combobox in my window with this list as its Itemssource:
<ComboBox Width="150" x:Name="cboObjectList"
ItemsSource="{Binding Path=ObjectList}" DisplayMemberPath="AName"/>
The problem is that when I select any item in the combobox, The SelectedIndex property is always = -1 and also the SelectedItem is null.
What am I doing wrong?
Found out the cause of the cause of the problem. There was an old line of code where I programmatically set the SelectedIndex to -1 :)
Related
I have a ComboBox defined like this:
<ComboBox
ItemsSource="{Binding Choices}"
SelectedItem="{Binding Value}"
Text="{Binding Text}"
IsEditable="True"
TextSearch.TextPath="Label"
DisplayMemberPath="Label" />
Here is my view Model:
public class ComboBoxViewModel : ViewModelBase
{
private string _selectedCode;
public ReadOnlyObservableCollection<ComboBoxItem> Choices { get; }
public ComboBoxItem Value
{
get { return this.Choices.FirstOrDefault(choice => choice.Code == _selectedCode); }
set
{
this.SetCode(value?.Code)
}
}
public string Text
{
get { return this.Value?.Label ?? _selectedCode; }
set
{
// Only set the code if no pre-defined code can be selected
if (this.Value == null)
{
this.SetCode(value)
}
}
}
public ComboBoxViewModel()
{
this.Choices = [..];
}
public bool SetCode(string code)
{
if (_selectedCode != code)
{
_selectedCode = code;
// Tried all the combination with/without/different order with no change
this.RaisePropertyChanged(nameof(this.Value));
this.RaisePropertyChanged(nameof(this.Text));
}
}
}
public class ComboBoxItem
{
public string Code { get; }
public string Label { get; }
public ComboBoxItem(string code, string label)
{
this.Code = code;
this.Label = label;
}
}
The Choices collection is initialized with some pair: Code,Label. I want to display the Label to the user and use the Code in my business layer. I also want my user to input its own code in the ComboBox (this is why the IsEditable dependency property is set to True and why I also bind Text on my ViewModel).
Everythings works fine when directly bind my ViewModel on the Control. The _selectedCode is updated prioritary with the selected Choices element or with the manual input if necessary.
My problem occurs when I pre-set the _selectedCode using the SetCode method. The Value property is no longer updated when I chose a new existing Choice in the ComboBox...
Is it possible to bind both SelectedItem and Text of a ComboBox? Do you have an idea why the bound properties are not updated after a programmatic initialization? It is like the event is not fired anymore...
Basic question from a novice. I've been stuck on this and have read through a lot of material and several similar questions on SO; hopefully not a completely duplicate question. I simplified the code as much as I know how to.
I'm trying to make the ListView show a filtered ObservableCollection) property (as the ItemsSource?), based on the selection in the ComboBox.
Specifically, which "meetings" have this "coordinator" related to it.
I'm not seeing any data errors in the output while it's running and debugging shows the properties updating correctly, but the ListView stays blank. I'm trying to avoid any code-behind on the View, there is none currently.
Thanks!
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Meeting> meetings;
public ObservableCollection<Meeting> Meetings
{
get
{
return meetings;
}
set
{
meetings = value;
OnPropertyChanged("ListProperty");
OnPropertyChanged("Meetings");
}
}
private string coordinatorSelected;
public string CoordinatorSelected
{
get
{
return coordinatorSelected;
}
set
{
coordinatorSelected = value;
Meetings = fakeDB.Where(v => v.CoordinatorName == CoordinatorSelected) as ObservableCollection<Meeting>;
}
}
private ObservableCollection<string> comboProperty = new ObservableCollection<string> { "Joe", "Helen", "Sven" };
public ObservableCollection<string> ComboProperty
{
get
{
return comboProperty;
}
}
private ObservableCollection<Meeting> fakeDB = new ObservableCollection<Meeting>() { new Meeting("Joe", "Atlas"), new Meeting("Sven", "Contoso"), new Meeting("Helen", "Acme") };
public ObservableCollection<Meeting> ListProperty
{
get
{
return Meetings;
}
}
public class Meeting
{
public string CoordinatorName { get; set; }
public string ClientName { get; set; }
public Meeting(string coordinatorName, string clientName)
{
CoordinatorName = coordinatorName;
ClientName = clientName;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<Window.Resources>
<local:ViewModel x:Key="VM"></local:ViewModel>
</Window.Resources>
<DockPanel DataContext="{StaticResource ResourceKey=VM}">
<ComboBox Margin="10" ItemsSource="{Binding ComboProperty}" SelectedItem="{Binding CoordinatorSelected}" DockPanel.Dock="Top"/>
<ListView Margin="10" ItemsSource="{Binding ListProperty, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="ClientName"/>
</DockPanel>
Update:
This seems to show that the lambda is returning a Meeting object but the assignment to Meetings is failing. Is this an error in casting maybe?
Thanks again.
You always have to change a property's backing field before you fire a PropertyChanged event. Otherwise a consumer of the event would still get the old value when it reads the property.
Change the Meetings property setter like this:
public ObservableCollection<Meeting> Meetings
{
get
{
return meetings;
}
set
{
meetings = value;
OnPropertyChanged("ListProperty");
OnPropertyChanged("Meetings");
}
}
I believe I've found two solutions to the same problem. The error pointed out #Clemens was also part of the solution. The Meetings property problem is solved if I change ListProperty and Meetings to IEnumerable. Alternatively this approach without changing the type, which I believe invokes the collection's constructor with the filtered sequence as an argument.
set
{
coordinatorSelected = value;
var filteredList = fakeDB.Where(v => v.CoordinatorName == coordinatorSelected);
Meetings = new ObservableCollection<Meeting>(filteredList);
OnPropertyChanged("ListProperty");
}
I am really struggling with data binding and the MVVM Methodology, though I like the concept I am just struggling. I have created a WPF for that has multiple comboboxes and a button. The first combobox will list database instance names. the remaining comboboxes will be populated after the button is clicked. Since I am having issues with the first, database instances, combobox I will only show my code for that. When the application starts up the combobox is loaded and the first item is selected, as expected. The issue is when I select a new name my method that I expect to get called does not. Can someone help me to understand why my method public DBInstance SelectedDBInstance is not getting executed when I have this in my XAML, SelectedValue="{Binding SelectedDBInstance, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}?
Here is my XAML for the database instances combobox. One question I have here is the "value" fpr SelectedValuePath, if I change it to say "DBInstanceName" it does not work.
<ComboBox x:Name="cbxRLFDBInstances" ItemsSource="{Binding DBInstances}"
SelectedValue="{Binding SelectedDBInstance, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="value" DisplayMemberPath="DBInstanceName"/>
Here is my ViewModel Code:
namespace DatabaseTest.ViewModel
{
class RLFDatabaseTableViewModel : INotifyPropertyChanged
{
Utilities dbtUtilities = new Utilities();
public RelayCommand LoadDBInfoCommand
{
get;
set;
}
public RLFDatabaseTableViewModel()
{
LoadDBInstances();
LoadDBInfoCommand = new RelayCommand(LoadDBInfo);
}
public ObservableCollection<DBInstance> DBInstances
{
get;
set;
}
public void LoadDBInstances()
{
ObservableCollection<DBInstance> dbInstances = new ObservableCollection<DBInstance>();
DataTable dt = SmoApplication.EnumAvailableSqlServers(false);
dbInstances.Add(new DBInstance { DBInstanceName = "fal-conversion\\mun2012ci" });
dbInstances.Add(new DBInstance { DBInstanceName = "fal-conversion\\mun2014ci" });
if (dt.Rows.Count > 0)
{
foreach (DataRow dr in dt.Rows)
{
dbInstances.Add(new DBInstance { DBInstanceName = dr["Name"].ToString() });
}
}
DBInstances = dbInstances;
}
private DBInstance _selectedDBInstance;
public DBInstance SelectedDBInstance
{
get
{
return _selectedDBInstance;
}
set
{
_selectedDBInstance = value;
RaisePropertyChanged("SelectedDBInstance");
//ClearComboBoxes();
}
}
}
}
Here is my Model code. When I step through the code this method, public string DBInstanceName, gets executed multiple time. I do not know why and it is seems wasteful to me.
namespace DatabaseTest.Model
{
public class RLFDatabaseTableModel { }
public class DBInstance : INotifyPropertyChanged
{
private string strDBInstance;
public override string ToString()
{
return strDBInstance;
}
public string DBInstanceName
{
get
{
return strDBInstance;
}
set
{
if (strDBInstance != value)
{
strDBInstance = value;
RaisePropertyChanged("DBInstanceName");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
You should bind the SelectedItem property of the ComboBox to the SelectedDBInstance property and get rid of the SelectedValuePath:
<ComboBox x:Name="cbxRLFDBInstances" ItemsSource="{Binding DBInstances}"
SelectedItem="{Binding SelectedDBInstance, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="DBInstanceName"/>
The SelectedValuePath property is only used when you want to bind to a source property that is not of the same type as the item in the ItemsSource collection.
To select an item initially you should set the SelectedDBInstance property to an item that is present in the DBInstances collection:
public RLFDatabaseTableViewModel()
{
LoadDBInstances();
LoadDBInfoCommand = new RelayCommand(LoadDBInfo);
SelectedDBInstance = DBInstances[0]; //selected the first item
}
I wanna bind a dictionary to a combobox.
My code is this (here I simplified it a little)
public class MyObject
{
private AnotherObject _obj;
public MyObject() { }
public MyObject(AnotherObject obj)
{
_obj = obj;
}
public string ObjType
{
get { return (_obj != null ? _obj.GetType() : "NO OBJECT" ); }
}
public string ObjString
{
get { return (_obj != null ? _obj.ToString() : "NO OBJECT" ); }
}
}
public class MyClass
{
private Dictionary<string, MyObject> _BindingButtons;
public MyClass()
{
_BindingButtons["KEY_1"] = new MyObject( CreateAnotherObject() );
_BindingButtons["KEY_2"] = new MyObject( CreateAnotherObject() );
...
...
}
public Dictionary<string, MyObject> BindingButtons
{
get { return _BindingButtons; }
}
}
Now, in the xaml I wrote
<ComboBox ItemsSource="{Binding BindingButtons}"
DisplayMemberPath="ObjString"
SelectedValuePath="KEY_1"
SelectedValue="{Binding ???? }"/>
and I have two problem
the combo list is empty (the dictionary is not empty)
Is there a way to bind the SelectedValue to something like:
"{Binding BindingButtons[KEY_1]}"
without create a property for each combo?
Where I wrong?
Regards.
[EDIT]
regarding my question on SelectedValue...
Suppose to have 10 combos with the same ItemsSource. Now, instead of write 10 properties (one for each combo), is there a way to write something like that (to handle SelectedItem):
class Test
{
private string[] _Items = new string[10];
public string GetItem(int index)
{
return _Items[index];
}
public void SetItem(int index, string value)
{
_Items[index] = value;
}
}
Dictionary has a collection property Values which you can leverage here
according to your data model this should work
<ComboBox ItemsSource="{Binding BindingButtons.Values}"
DisplayMemberPath="ObjString"
SelectedItem="{Binding SelectedItemInVM}"/>
I was not sure about the purpose of SelectedValue in your question, I replaced it with SelectedItem in example, you may adjust according to your needs.
EDIT
answer for your edit
you have two options to bind the individual combobox to an array
but before that you need to convert the array field to a public property and initialize accordingly, otherwise binding may fail
public string[] Items { get; set; }
and then bind using indexers Items[0] etc.
<ComboBox ItemsSource="{Binding BindingButtons.Values}"
DisplayMemberPath="ObjString"
SelectedValuePath="ObjType"
SelectedValue="{Binding Items[0]}"/>
or create the array of MyObject type
public MyObject[] Items { get; set; }
and binding remain same but using SelectedItem property instead of SelectedValue
<ComboBox ItemsSource="{Binding BindingButtons.Values}"
DisplayMemberPath="ObjString"
SelectedItem="{Binding Items[0]}"/>
make sure to initialize your array in constructor Items = new string[10]; or Items = new MyObject[10]; 10 is number of combo you have
I have an issue related to combobox in WPF
My xaml code for Combo box
<ComboBox Name="CertificateComboBox" Grid.Row="2" Grid.Column="2" VerticalAlignment="Center" Margin="1,59,0,48" IsEnabled="{Binding SecurityEnabled}"
ItemsSource="{Binding CertificatesList}" DisplayMemberPath="CertName" SelectedItem="{Binding Certificate, Mode=TwoWay}" ToolTip="List of SSL certificates. Select a value from the combobox.">
</ComboBox>
CertificatesList is a list of CertificateEntry objects
public class CertificateEntry
{
public string CertName { get; set; }
public string CertHash { get; set; }
public X509Certificate2 certificte {get; set; }
public CertificateEntry( X509Certificate2 cert)
{
certificte = cert;
if (cert.FriendlyName.Equals(""))
{
CertName = cert.Issuer;
}
else
{
CertName = cert.FriendlyName;
}
CertHash = cert.Thumbprint;
}
public string ToString()
{
return CertName;
}
}
Property SelectedItem Property is
public CertificateEntry Certificate
{
get
{
return _certificate;
}
set
{
if (_certificate == value)
return;
_certificate = value;
OnPropertyChanged("Certificate");
}
}
My issue is when i am trying to assign a object to CertificateComboBox.SelectedItem
this.CertificateComboBox.SelectedItem = _certificate;
where _certificate is a CertificateEntry Object
Its not taking the value
In the add watch after the above call is `null
this.CertificateComboBox.SelectedItem = null
the assignment is not happening,
I want to show the assigned certificate as the default selected value in combobox, which is not happening
you can only set the selected Item to an object that is in your ItemsSource, is this the case?
Regards
Dominik
I am confused...
You created property Certificate for your combo's selected item but you assign its SelectedItem property like that:
this.CertificateComboBox.SelectedItem = _certificate;
where probably _certicate is null.
I think you just need to do
Certificate = some value
where some value is a valid CertificateEntry that belongs to your items source.