I would have thought this question would have been answered already, but I am not finding it. I have a simple class:
public class BinanceEndpoint : ObservableObject
{
private string baseAddress;
private string socketBaseAddress;
public string BaseAddress
{
get { return baseAddress; }
set { baseAddress = value; RaisePropertyChangedEvent(nameof(BaseAddress)); }
}
public string SocketBaseAddress
{
get { return socketBaseAddress; }
set { socketBaseAddress = value; RaisePropertyChangedEvent(nameof(SocketBaseAddress)); }
}
}
I am then populating an ObservableDictionary with objects from that class:
private MainViewModel()
{
private BinanceEndpoint apiEndPoint;
private ObservableDictionary<string, BinanceEndpoint> endPoints = new ObservableDictionary<string, BinanceEndpoint>();
endPoints.Add("Binance.com", new BinanceEndpoint() { BaseAddress = "https://api.binance.com", SocketBaseAddress = "wss://stream.binance.com:9443", });
endPoints.Add("Binance.us", new BinanceEndpoint() { BaseAddress =
"https://api.binance.us", SocketBaseAddress = "wss://stream.binance.us:9443", });
endPoints.Add("Testnet", new BinanceEndpoint() { BaseAddress = "https://testnet.binance.vision", SocketBaseAddress = "wss://testnet.binance.vision", });
}
[JsonIgnore]
public ObservableDictionary<string,BinanceEndpoint> EndPoints
{
get { return endPoints; }
set { endPoints = value; RaisePropertyChangedEvent(nameof(EndPoints)); }
}
public BinanceEndpoint APIEndPoint
{
get { return apiEndPoint; }
set { apiEndPoint = value; RaisePropertyChangedEvent(nameof(APIEndPoint)); }
}
}
Then I am trying to populate a ComboBox from using the ObservableDictionary.
<ComboBox Grid.Column="1" ItemsSource="{Binding EndPoints}" DisplayMemberPath="Key" SelectedValuePath="Value" SelectedValue="{Binding Path=APIEndPoint, Mode=TwoWay}"/>
The issue I am having is that the SelectedValue does not update the value of the ComboBox when it is loaded. What am I doing wrong?
You should avoid binding to a Dictionary in the first place. There is a reason that there is no ObservableDictionary in the .NET library - since the year of release in 2002. I guess we agree that this is not because MS developers didn't know how to implement such a dictionary after all these years.
Your are using SelectedValuePath and SelectedValue wrong. SelectedValue returns the value of the property that SelectedValuePath is pointing to. SelectedValuePath provides a property path on the SelectedItem.
When you actively set the SelectedValue, then the control will try to find and select the item where the item's property specified by SelectedValuePath matches the value of SelectedValue.
When SelectedItem returns the selected item (instance), then SelectedValue returns the value of a property on this SelectedItem that is specified using SelectedValuePath.
For example: we bind to a Dictionary<string, BinanceEndpoint>. To display the Key of your Dictionary we specify the DisplayMemberPath (as an alternative to a DataTemplate).
The SelectedItem will hold KeyValuePair<string, BinanceEndpoint>.
To have the ComboBox.SelectedValue return the selected item's SocketBaseAddress value, we must set the SelectedValuePath to "Value.SocketBaseAddress":
<ComboBox x:Name="ComboBox"
ItemsSource="{Binding EndPoints}"
DisplayMemberPath="Key"
SelectedValuePath="Value.SocketBaseAddress" />
<!-- Display the selected item's SocketBaseAddress value -->
<TextBlock text="{Binding ElementName=ComboBox, Path=SelectedValue}" />
If you want the SelectedValue to return the BinanceEndpoint instance then set SelectedValuePath to "Value":
<ComboBox x:Name="ComboBox"
ItemsSource="{Binding EndPoints}"
DisplayMemberPath="Key"
SelectedValuePath="Value" />
<!-- Display the selected item's 'SocketBaseAddress' value -->
<TextBlock text="{Binding ElementName=ComboBox, Path=SelectedValue.SocketBaseAddress}" />
Related
I have a Listbox with property names of type Color from a MySettings object. I want to create another control (let's say a TextBox, the type is irrelevant) that binds to the value of property name listed.
I am at a loss as to how to achieve that. As it is now, the listbox is just a list of strings, obviously, it has no knowledge of the MySettings object.
One of the things I thought about is creating a collection of objects of a custom type that somehow binds to the MySettings object, and setting that as ItemsSource for the ListBox.
I know I trying to reinvent the wheel here, so I would appreciate any pointers to get me started.
Code-behind, ListBox is currently defined in xaml with no properties other than x:Name="listBox"
foreach (var prop in settings.GetType().GetProperties()
.Where(p => p.PropertyType == typeof(System.Drawing.Color))
.OrderBy(p => p.Name))
{
listBox.Items.Add(new ListBoxItem()
{
Content = prop.Name,
});
}
MySettings contains several properties:
[UserScopedSetting()]
[ConfigurationProperty(nameof(BackColor), DefaultValue = "GhostWhite")]
public System.Drawing.Color BackColor
{
get { return (System.Drawing.Color)base[nameof(BackColor)]; }
set
{
base[nameof(BackColor)] = value;
OnPropertyChanged();
}
}
[UserScopedSetting()]
[ConfigurationProperty(nameof(ForeColor), DefaultValue = "Black")]
public System.Drawing.Color ForeColor
{
get { return (System.Drawing.Color)base[nameof(ForeColor)]; }
set
{
base[nameof(ForeColor)] = value;
OnPropertyChanged();
}
}
You could assign the ListBox's ItemsSource to a collection of name/value pairs (an anonymous class here, but could as well be a regular class):
listBox.ItemsSource = settings.Properties
.OfType<SettingsProperty>()
.Where(p => p.PropertyType == typeof(System.Drawing.Color))
.Select(p => new
{
Name = p.Name,
Value = settings[p.Name]
});
Then declare a ListBox ItemTemplate that shows the Name property, and a TextBlock that binds to the Value property of the selected ListBox item:
<ListBox x:Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Text="{Binding ElementName=listBox, Path=SelectedItem.Value}"/>
I want to set selected value of a combobox
I am receiving a DataTable from the database looking like this:
this Datatable is bound to this combobox.
<ComboBox
DisplayMemberPath="KommuneNavn"
SelectedValuePath="KommuneNr"
ItemsSource="{Binding KommuneNavne}"
SelectedValue="{Binding KommuneNr, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="3"
IsEnabled="{Binding IsUdenlandskAdresse, Converter={StaticResource BooleanNotConverter}}" />
In my viewmodel I have a specific KommuneNr stored in a property. I would like to have my combobox set to show the KommuneNavn that matches with this KommuneNr.
Example:
I have the KommuneNr 101 stored in my viewmodel, the KommuneNavn that matches with this is København I would then like to have my combobox be set to København.
This was pretty difficult to explain, I hope I am making sense. Otherwise feel free to ask.
KommuneNavne must be an ObservableCollection, and in your ViewModel you should implement INotifyPropertyChanged (not described in this example)
<ComboBox
ItemsSource="{Binding KommuneNavne}"
SelectedValue="{Binding KommuneNr, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="KommuneNavn"
SelectedValuePath="KommuneNr"
/>
ViewModel
public class MyViewModel : INotifyPropertyChanged
{
public ObservableCollection<KommuneNavn> KommuneNavne
{
get { value = _kommuneNavne; } //=> _kommuneNavne;
set
{
_kommuneNavne = value;
OnPropertyChanged(nameof(KommuneNavne));
}
}
public long KommuneNr
{
get { value = _kommuneNr; } //=> _kommuneNr;
set
{
if (_kommuneNr == value) return;
_kommuneNr = value;
OnPropertyChanged(nameof(KommuneNr));
}
}
private void SetValue(int valueToSet)
{
KommuneNr = valueToSet;
}
}
For some reason when I bind a ComboBox to a list of POCOs, the property which is bound to SelectedValue is set twice:
With value = POCO.ToString()
With value = POCO.Key property, which is the intended behaviour
I have the following ComboBox that is bound to properties in my ViewModel:
<ComboBox ItemsSource="{Binding Path=AllowedClassifications}"
DisplayMemberPath="Value"
SelectedValue="{Binding Path=TargetGroup.Classification}"
SelectedValuePath="Key" />
The properties in ViewModel are defined as:
//ICollection is implemented by ObservableCollection<T>
//DataBaseFieldValue has two public properties: string Key, string Value
public ICollection<DatabaseFieldValue> AllowedClassifications
{
get { return _allowedClassifications; }
private set { _allowedClassifications = value; }
}
public Model.TargetGroup TargetGroup
{
get { return _targetGroup; }
private set { _targetGroup = value; OnPropertyChanged("TargetGroup"); }
}
TargetGroup.Classification is defined as:
public string Classification
{
get { return _classification; }
set
{
System.Diagnostics.Debug.WriteLine("Classification: " + value);
_classification = value;
OnPropertyChanged("Classification");
}
}
Debug output:
Classification:
MyNamespace.DatabaseFieldValue
Classification: 2
What's happening here? Am I doing this completely wrong?
Everything looks OK in your code, except for the fact that according to your XAML the property which is bound to SelectedValue should be set to the POCO.Key value rather than POCO.Value (as you wrote you expected). I have just created a test project with similar setup and everything works.
Alternatively, you could try using SelectedItem property of combobox in combination with ItemTemplate:
<ComboBox ItemsSource="{Binding Path=AllowedClassifications}"
SelectedItem="{Binding Path=TargetGroup.Classification}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
In this case the TargetGroup.Classification property must be of type DatabaseFieldValue.
i have a combox control which is bound to a property using MVVM. There is validation done in the set method on value change.. The problem is the value getting changed to new value even if the validation fails and not retaining the old value..
Below is the XAML:
<ComboBox Grid.Column="1" Grid.Row="1" Width="200" ItemsSource="{Binding Path=Applications, Mode=OneTime}" SelectedItem="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.Application, Mode=TwoWay}" Margin="3"></ComboBox>
Below is the View Model Code:
private string[] types = new string[] { "A", "B" };
private string application;
public ObservableCollection<string> Applications { get; private set; }
public Const() {
this.Applications = new ObservableCollection<string>(this.types.ToList());
}
public string Application {
get {
this.application = this.applicationSpecificRequirements.ContainsKey(Resources.ApplicationKey) ? this.applicationSpecificRequirements[Resources.ApplicationKey] : this.Applications[0];
return this.application;
}
set {
if (this.exchangeViewModel.CheckIfApplicationNameExistsOrIsEmptyAndAssign(this.InstanceName, value)) {
System.Windows.Application.Current.Dispatcher.BeginInvoke(
new Action(() => {
this.applicationSpecificRequirements[Resources.ApplicationKey] = this.application;
((IHaveOnPropertyChangedMethod) this).OnPropertyChanged("Application");
}), DispatcherPriority.ContextIdle, null);
return;
}
this.applicationSpecificRequirements[Resources.ApplicationKey] = value;
}
}
looks like you're missing OnPropertyChanged(...) at last line in property setter.
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.