Everytime i change my selected item inside my UI it's not updating my combobox.
XAML
<ComboBox x:Name="CompanyComboBox" HorizontalAlignment="Left" Height="26"
Margin="100,41,0,0" VerticalAlignment="Top" Width="144"
SelectionChanged="CompanyComboBox_SelectionChanged"
SelectedItem="{Binding SelectedCompany, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}">
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentPresenter
Content="{Binding Converter={StaticResource DescriptionConverter}}">
</ContentPresenter>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
C#
private Company _selectedCompany;
public Company SelectedCompany
{
get { return _selectedCompany; }
set
{
if (value == _selectedCompany)
return;
_selectedCompany = value;
OnPropertyChanged(nameof(SelectedCompany));
}
}
Just to clarify the Company class is actually an ENUM
DescriptionConverter:
public class CompanyDescriptionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var type = typeof(Company);
var name = Enum.GetName(type, value);
FieldInfo fi = type.GetField(name);
var descriptionAttrib = (DescriptionAttribute)
Attribute.GetCustomAttribute(fi, typeof(DescriptionAttribute));
return descriptionAttrib.Description;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
what i mean by inside my UI, is i have a list of companies set to a combobox item source, and when i change the combobox value to another company, it doesn't update in my source, it stay's as default.
My Enum might clarify the problem for someone:
[Description("Netpoint Solutions")]
Company1 = 0,
[Description("Blackhall Engineering")]
Company2 = 180,
Try to remove Mode=OneWayToSource and your event handler:
SelectionChanged="CompanyComboBox_SelectionChanged"
You don't need to handle the SelectionChanged event when you bind the SelectedItem property.
Also make sure that you set the DataContext to an instance of your class where the SelectedCompany property is defined.
Your Binding is defined as OneWayToSource. When you want it to be updated from the viewModel, you should set it to TwoWay.
See the documentation of Bindings for more details: https://msdn.microsoft.com/de-de/library/ms752347(v=vs.110).aspx
EDIT:
I skipped the part where you have an Enum as the source. I do not see, that you define the ItemsSource for the Combobox, where is this done? The value passed to the setter of SelectedCompany has to be in the Collection defined as ItemsSource.
For Enums, you can refer to this thread: How to bind an enum to a combobox control in WPF?
Related
I've been searching for a solution to display the indexes of items from a ListView for a few hours. I can't add a new property to the data source, as an index property to bind to the value.
I've been trying to Bind to a Converter:
<DataTemplate x:Key="TubeTemplate" x:DataType="data:Tube">
<local:TubeTemplate HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
FavoritesNumber="{Binding Converter={StaticResource IndexConverter}}"
Closed="TubeTemplate_Closed"></local:TubeTemplate>
</DataTemplate>
This is the Converter:
public sealed class IndexConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var item = (ListViewItem)value;
var listView = ItemsControl.ItemsControlFromItemContainer(item) as ListView;
int index = listView.IndexFromContainer(item) + 1;
return index.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
The issue is that my code breaks at: var item = (ListViewItem)value;
The value I'm getting is the DataType binded to each item, instead of the ListViewItem.
What Am I doing wrong?
Use {RelativeSource Mode=TemplatedParent} in the Binding. Then, you can get the ItemContainer with the help of VisualTreeHelper as follows.
<local:TubeTemplate ...
FavoritesNumber="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource IndexConverter}}"
.../>
and
public object Convert(object value, Type targetType, object parameter, string language)
{
var presenter = value as ListViewItemPresenter;
var item = VisualTreeHelper.GetParent(presenter) as ListViewItem;
var listView = ItemsControl.ItemsControlFromItemContainer(item);
int index = listView.IndexFromContainer(item) + 1;
return index.ToString();
}
However, index displayed in this way won't be automatically updated on collection changes. So, if you'll remove some items afterwards, you'd have to implement another function to request each item to reload its index.
Try using
AlternationIndex. Also according to this answer, you should use ListViewItem as RelativeSource
In your case, it would look something like
<DataTemplate>
<TextBlock Text="{Binding Path=(ItemsControl.AlternationIndex),
RelativeSource={RelativeSource AncestorType=ListViewItem},
StringFormat={}Index is {0}}">
</TextBlock>
</DataTemplate>
I have a ComboBox that in read-only mode is used to just display a single value (string) and becomes disabled, this is the current implementation:
<ComboBox Name="cmbSalesDocuments" SelectedValuePath="SalesDocumentId" SelectedValue="{Binding Path=SalesDocumentId, UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True,NotifyOnValidationError=True}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SalesDocumentAName}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
in this mode I'm not going to bind it with the same object as the write-mode object, but bound to a more lighter object that contains a string property SalesDocumentAName. The problem is that I can't set the Displayed and Selected value to that property without using the ItemsSource collection that needs an IEnumerable object.
You can write an IValueConverter to get a collection from a single item
public class ObjectToCollectionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var collection = new List<object>();
if (value != null)
{
collection.Add(value);
}
return collection;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Now you can bind the ComboBox with
<ComboBox
ItemsSource="{Binding SalesDocumentAName,Converter={StaticResource ObjectToCollectionConverter}"
SelectedItem="{Binding SalesDocumentAName, Mode=OneWay}"
IsReadOnly="true"/>
You can't bind a ComboBox to a string value. The ItemsSource property can only be bound to an IEnumerable. You could remove all items but one string from the IEnumerable though.
The other option would be to use another Control or modify the ControlTemplate. A ComboBox is not a TextBlock, unless you make it look like one.
if you don't set ItemsSource, you can add a ComboBoxItem with Content set to light-weight object:
<ComboBox Name="cmbSalesDocuments" SelectedValuePath="SalesDocumentId"
SelectedValue="{Binding Path=SalesDocumentId, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True,NotifyOnValidationError=True}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SalesDocumentAName}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.Items>
<ComboBoxItem Content="{Binding Path=SalesDocument}"
ContentTemplate="{Binding ItemTemplate, ElementName=cmbSalesDocuments}"/>
</ComboBox.Items>
</ComboBox>
note: ItemsSource or Items - only one property can be used at a time
I have a combobox with StaticResource called Currencies, Currencies is an ObservableCollection of some class
in this class i have a parameter called CurrencyID.
I have another class called Item which have a parameter called CurrencyID.
i want to set the Item.CurrencyID from the combobox, for that i use ValueSelected and ValueSelectedPath with a converter from comboboxitem to the CurrencyID.
and it works.
next i want to select the comboboxitem from the CurrencyID of the Item class.
But it always show unselected comboboxitem.
Here's the relevant code:
XAML:
<Window.Resources>
<converters:sp_GetCurrencies_ResultToID x:Key="sp_GetCurrencies_ResultToIDConverter"/>
<CollectionViewSource x:Key ="Currencies" Source="{Binding Currencies}"/>
</Window.Resources>
<ComboBox x:Name="cmbCurrencies" ItemsSource="{Binding Source={StaticResource Currencies} }" SelectedValuePath="{Binding CurrencyID, Mode=TwoWay}" SelectedValue="{Binding Item.CurrencyID, Converter={StaticResource sp_GetCurrencies_ResultToIDConverter}}" DisplayMemberPath="CurrencyName" Width="150" Height="30" VerticalContentAlignment="Center" FlowDirection="RightToLeft" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="5"/>
Converter:
public class sp_GetCurrencies_ResultToID : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
using (PricingManagerEntities db = new PricingManagerEntities())
return db.sp_GetCurrencies((long)value,null).FirstOrDefault();
else
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
return (value as sp_GetCurrencies_Result).CurrencyID;
}
else return null;
}
}
Inside the code i set Item.CurrencyID = 1.
I'm getting also this error, in which i think it's related:
BindingExpression path error: 'CurrencyID' property not found on 'object' ''ItemsViewModel' (HashCode=45202739)'. BindingExpression:Path=CurrencyID; DataItem='ItemsViewModel' (HashCode=45202739); target element is 'ComboBox' (Name='cmbCurrencies'); target property is 'SelectedValuePath' (type 'String')
The program '[12436] PricingManager.vshost.exe' has exited with code 0 (0x0).
Thank you for your help.
I'm using ComboBox in my xaml but i'm unable to visualise any data on the view. It just shows empty text file and empty dropdown.
I have tried to debug the problem with help of these tips. However, I haven't been able to solve the issue.
Here's the databindingDebugConverter:
public class DatabindingDebugConverter : IValueConverter
{
public object Convert(object value1, Type targetType, object parameter, CultureInfo culture)
{
Debugger.Break();
return value1;
}
public object ConvertBack(object value2, Type targetType, object parameter, CultureInfo culture)
{
Debugger.Break();
return value2;
}
}
The Value1 is returning in ComboBox Text= case a "Field Device" (object{string})
and on the ItemsSource= value1 is returning object{Device} with the fields of Category and reference to Category1 object holding CategoryId in it.
For the SelectedValue a "Field Device" (object{string}) is once again returned.
Here is the ComboBox xaml:
<ComboBox x:Name="ProductCategoryComboBox" HorizontalAlignment="Right" Height="21.96" Margin="0,20,10.5,0" VerticalAlignment="Top" Width="100"
Text="{Binding DeviceDatabaseViewModel.SelectedDevice.Category, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource debugConverter}}"
IsEditable="False"
ItemsSource="{Binding DeviceDatabaseViewModel.SelectedDevice, Converter={StaticResource debugConverter}}"
SelectedValue="{Binding DeviceDatabaseViewModel.SelectedDevice.Category, Mode=TwoWay, Converter={StaticResource debugConverter}}"
SelectedValuePath="CategoryId"
DisplayMemberPath="Category" />
Similar binding including TextBlock fields within the xaml are working fine and displaying the string values from the SelectedDevice.
EDIT
The selectedDevice is referred from a dataGrid:
private Device _selectedDevice;
public Device SelectedDevice
{
get
{
return _selectedDevice;
}
set
{
if (_selectedDevice == value)
{
return;
}
_selectedDevice = value;
RaisePropertyChanged("SelectedDevice");
}
}
A Combobox is for choosing an Item out of a Collection (for example List or ObservableCollection if you want the UI to recognize changes in the collection).
You do not bind to a collection here:
ItemsSource="{Binding DeviceDatabaseViewModel.SelectedDevice, Converter={StaticResource debugConverter}}"
Instead of binding to the SelectedDevice you would need to bind to an ObservableCollection AllDevices or something like this to which you then could bind the ItemsSource.
Here an example for something you could bind to:
public class DeviceDatabaseViewModel
{
public ObservableCollection<Device> AllDevices
{
get; set;
}
public DeviceDatabaseViewModel()
{
AllDevices = new ObservableCollection<Device>();
AllDevices.Add(new Device { Category = 'Computer', CategoryId = 1 }, new Device { Category = 'Tablet', CategoryId = 2 });
}
}
Then using the following binding:
ItemsSource="{Binding DeviceDatabaseViewModel.AllDevices, Converter= {StaticResource debugConverter}}"
I'm using the following control template in two windows that are opened at the same time and both using the SAME viewmodel.
Here is the template;
<ControlTemplate x:Key="SecurityTypeSelectionTemplate">
<StackPanel>
<RadioButton GroupName ="SecurityType" Content="Equity"
IsChecked="{Binding Path=SecurityType, Mode=TwoWay, Converter={StaticResource EnumBoolConverter}, ConverterParameter=Equity}" />
<RadioButton GroupName ="SecurityType" Content="Fixed Income"
IsChecked="{Binding Path=SecurityType, Mode=TwoWay, Converter={StaticResource EnumBoolConverter}, ConverterParameter=FixedIncome}" />
<RadioButton GroupName ="SecurityType" Content="Futures"
IsChecked="{Binding Path=SecurityType, Mode=TwoWay, Converter={StaticResource EnumBoolConverter}, ConverterParameter=Futures}" />
</StackPanel>
</ControlTemplate>
Here is the viewmodel property:
private SecurityTypeEnum _securityType;
public SecurityTypeEnum SecurityType
{
get { return _securityType; }
set
{
_securityType = value; RaisePropertyChanged("SecurityType");
}
}
Here's the Enum:
public enum SecurityType { Equity, FixedIncome, Futures }
Here is the converter:
public class EnumToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object enumTarget, CultureInfo culture)
{
string enumTargetStr = enumTarget as string;
if (string.IsNullOrEmpty(enumTargetStr))
return DependencyProperty.UnsetValue;
if (Enum.IsDefined(value.GetType(), value) == false)
return DependencyProperty.UnsetValue;
object expectedEnum = Enum.Parse(value.GetType(), enumTargetStr);
return expectedEnum.Equals(value);
}
public object ConvertBack(object value, Type targetType, object enumTarget, CultureInfo culture)
{
string expectedEnumStr = enumTarget as string;
if (expectedEnumStr == null)
return DependencyProperty.UnsetValue;
return Enum.Parse(targetType, expectedEnumStr);
}
}
The problem is a bit strange. I have two windows that are showing slightly different views of the SAME ViewModel. The same template shown above is reused in both views.
If Equity is initially set as SecurityType, i can change this to FixedIncome by clicking on the relevant radio button. I can not then change it back to Equity.
I can however set it to Futures. But then after that, i can not change it to either FixedIncome or Equity by clicking the relevant radio buttons.
What's happening in the cases where i can not set change it back is that the Setter is called twice. the first time it's setting the value to the correct selected value, but the moment RaisePropertyChanged is fired,
the setter is invoked again, this time with the original value.
It feels like when RaisePropertyChanged, the the setter is being called by the binding from the 2nd window, thus overwriting the value being set in the first window where the user makes the selection.
Does anyone know if this is the case and how to avoid in this scenario?
Here's my version of EnumToBoolConverter:
public class EnumToBoolConverter : BaseConverterMarkupExtension<object, bool>
{
public override bool Convert(object value, Type targetType, object parameter)
{
if (value == null)
return false;
return value.Equals(Enum.Parse(value.GetType(), (string)parameter, true));
}
public override object ConvertBack(bool value, Type targetType, object parameter)
{
return value.Equals(false) ? DependencyProperty.UnsetValue : parameter;
}
}
The default behavior for a RadioButton is to update the source when the property changes so both windows are trying to update the source. One fix is to only update the source only from where the user clicked. To do this use Binding.UpdateSourceTrigger Explict on the binding. Add a click handler in code behind for RadioButton. In it explicity update the source.
<StackPanel>
<RadioButton GroupName ="SecurityType" Content="Equity"
IsChecked="{Binding Path=SecurityType, Mode=TwoWay, Converter={StaticResource EnumToBoolConverter}, UpdateSourceTrigger=Explicit, ConverterParameter=Equity}" Click="RadioButton_Click" />
<RadioButton GroupName ="SecurityType" Content="Fixed Income"
IsChecked="{Binding Path=SecurityType, Mode=TwoWay, Converter={StaticResource EnumToBoolConverter}, UpdateSourceTrigger=Explicit, ConverterParameter=FixedIncome}" Click="RadioButton_Click"/>
<RadioButton GroupName ="SecurityType" Content="Futures"
IsChecked="{Binding Path=SecurityType, Mode=TwoWay, Converter={StaticResource EnumToBoolConverter}, UpdateSourceTrigger=Explicit, ConverterParameter=Futures}" Click="RadioButton_Click"/>
</StackPanel>
private void RadioButton_Click(object sender, RoutedEventArgs e)
{
BindingExpression be = ((RadioButton)sender).GetBindingExpression(RadioButton.IsCheckedProperty);
be.UpdateSource();
}
You may have to use a UserControl instead of or inside your ControlTemplate to get code behind in your view.