WPF selecteditem value binding - c#

I have combobox with binded dictionary
Dictionary:
public Dictionary<string,DateTime> TimeItems { get; set; }
<ComboBox Grid.Column="3"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
ItemsSource="{Binding TimeItems}"
SelectedIndex="0"
SelectedItem="{Binding SelectedItem}">
How can i bind to public DateTime SelectedItem { get; set; } value of TimeItems

You can use converter to bind the the value of Dictionary to SelectedItem.
public class DictionaryValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((KeyValuePair<string, DateTime>)value).Value;
}
}
XAML
<ComboBox Grid.Column="3" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
ItemsSource="{Binding TimeItems}"
SelectedItem="{Binding SelectedItem, Converter={StaticResource DictionaryValueConverter}}" />

You can set SelectedValuePath to "Value" (since each item is a KeyValuePair) and then bind SelectedValue property to your property:
<ComboBox Grid.Column="3"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
ItemsSource="{Binding TimeItems}"
SelectedIndex="0"
SelectedValuePath="Value"
SelectedValue="{Binding SelectedItem}"/>

If you are only interested in displaying and retrieving the values of the Dcitionary then you can use the below option
ViewModel
class ExpenseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
var pc = PropertyChanged;
if (pc != null)
{
pc(this, new PropertyChangedEventArgs(propName));
}
}
private void Populate()
{
_mySource.Add("Test 1", DateTime.Now);
_mySource.Add("Test 2", DateTime.Now.AddDays(1));
_mySource.Add("Test 3", DateTime.Now.AddDays(2));
_mySource.Add("Test 4", DateTime.Now.AddDays(3));
NotifyPropertyChanged("MySource");
}
private Dictionary<string, DateTime> _mySource;
public Dictionary<string, DateTime> MySource
{
get { return _mySource; }
}
public ExpenseViewModel()
{
_mySource = new Dictionary<string, DateTime>();
Populate();
}
public DateTime SelectedDate
{
get;
set;
}
}
View.xaml
<StackPanel HorizontalAlignment="Left">
<StackPanel.Resources>
<local:Converters x:Key="Converter"/>
</StackPanel.Resources>
<ComboBox x:Name="cmbBox" Width="200" Margin="10,0,20,0" ItemsSource="{Binding MySource, Converter={StaticResource Converter}}"
SelectedItem="{Binding SelectedDate, Mode=TwoWay}" Height="20">
</ComboBox>
</StackPanel>
where local is the xmlns of the project containing the converter
Converter
public class Converters : IValueConverter
{
private static readonly PropertyInfo InheritanceContextProp = typeof(DependencyObject).GetProperty("InheritanceContext", BindingFlags.NonPublic | BindingFlags.Instance);
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var dic = value as Dictionary<string, DateTime>;
return dic.Values;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
In view behind code I have just defined the datacontext

Try this:
foreach (KeyValuePair<string,DateTime> values in TimeItems){
comboBox.Items.Add(values.Value);
}

Related

How to use an IMultiValueConverter to look up data from a related table

I have a DataGridTemplateColumn in a DataGrid that looks like this:
<DataGridTemplateColumn Width="3*" Header="Item">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding AssetDescriptionID} Converter={StaticResource converter}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox
DisplayMemberPath="Description"
ItemsSource="{Binding Data.AssetDescriptions, Source={StaticResource proxy}}"
SelectedValue="{Binding AssetDescriptionID}"
SelectedValuePath="AssetDescriptionID" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
I would like to use a Multi-Value Converter to lookup an Asset Description from another table by the Asset Description ID, in this (probably wrong) line:
<TextBlock Text="{Binding AssetDescriptionID} Converter={StaticResource converter}" />
The ViewModel has a public property containing the Asset Descriptions:
public IEnumerable<AssetDescription> AssetDescriptions { get; set; }
Where AssetDescription is essentially:
public class AssetDescription
{
public int AssetDescriptionID { get; set; }
public string Description { get; set; }
}
So I've written this converter class:
public class AssetDescriptionConverter : FrameworkElement, IMultiValueConverter
{
public IEnumerable<T_AssetDescription> AssetDescriptions
{
get { return (IEnumerable<T_AssetDescription>)GetValue(AssetDescriptionsProperty); }
set { SetValue(AssetDescriptionsProperty, value); }
}
public static readonly DependencyProperty AssetDescriptionsProperty =
DependencyProperty.Register("AssetDescriptions", typeof(IEnumerable<T_AssetDescription>), typeof(AssetDescriptionConverter));
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var items = values as IEnumerable<T_Asset>;
if (items != null)
var item = items.FirstOrDefault(x => x.AssetDescriptionID == (int)parameter);
return item == null ? string.Empty : item.Description;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
But I can't figure out how to declare this resource in the XAML and then bind it to my TextBlock.
In the resource part:
<AssetDescriptionConverter x:Key="AssetDescriptionConverter"/>
and then in xaml:
<TextBlock Text="{Binding AssetDescriptionID, Converter={StaticResource AssetDescriptionConverter}}"/>

WPF: Bind TabControl SelectedIndex to a View Model's Enum Property

I have a property on my ViewModel that is an enum:
ViewModel:
public MyViewModel {
// Assume this is a DependancyProperty
public AvailableTabs SelectedTab { get; set; }
// Other bound properties
public string Property1 { get; set; }
public string Property2 { get; set; }
public string Property3 { get; set; }
}
public enum AvailableTabs {
Tab1,
Tab2,
Tab3
}
I'd like to be able to bind SelectedIndex (or SelectedItem) of my TabControl to this property and have it correctly set the appropriate tab using a converter. Unfortunately, I'm a bit stuck. I know I can easily just use the SelectedIndex in my model, but I want the flexibility of re-ordering the tabs without breaking anything. I've given each TabItem a Tag property of the applicable enum value.
My XAML:
<TabControl Name="MyTabControl" SelectedIndex="{Binding SelectedTab, Converter={StaticResource SomeConverter}}">
<TabItem Header="Tab 1" Tag="{x:Static local:AvailableTabs.Tab1}">
<TextBlock Text="{Binding Property1}" />
</TabItem>
<TabItem Header="Tab 2" Tag="{x:Static local:AvailableTabs.Tab2}">
<TextBlock Text="{Binding Property2}" />
</TabItem>
<TabItem Header="Tab 3" Tag="{x:Static local:AvailableTabs.Tab3}">
<TextBlock Text="{Binding Property3}" />
</TabItem>
</TabControl>
My problem is that I can't figure out how to get the TabControl into my converter so I can do:
// Set the SelectedIndex via the enum (Convert)
var selectedIndex = MyTabControl.Items.IndexOf(MyTabControl.Items.OfType<TabItem>().Single(t => (AvailableTabs) t.Tag == enumValue));
// Get the enum from the SelectedIndex (ConvertBack)
var enumValue = (AvailableTabs)((TabItem)MyTabControl.Items[selectedIndex]).Tag;
I'm afraid I might be overthinking it. I tried using a MultiValue converter without much luck. Any ideas?
You simply need a converter that casts the value to an index.
public class TabConverter : IValueConverter
{
public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
{
return (int)value;
}
public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
{
return (AvailableTabs)value;
}
}
Instead of specifying the values in the XAML, I would bind ItemsSource to an array of values from your enum:
Code:
public AvailableTabs[] AvailableTabs => Enum.GetValues(typeof(AvailableTabs Enum)).Cast<AvailableTabs>().ToArray();
XAML:
<TabControl Name="MyTabControl" SelectedIndex="{Binding SelectedTab}" ItemsSource="{Binding AvailableTabs}" />
The important thing is that the solution should work if I change the tab order in the TabControl
if this is not important then the elements in the enum type
and the views in the TabControl have to be in the same order
the conversion in this case is to cast the enum value to int value
I name the views in TabControl according to enum values
AppTab.ValidDates (enum value) corresponds to validDatesView (view name)
enum values
public enum AppTab
{
Parameters, ValidDates, ...
}
views in TabControl
<TabControl Name="myTabControl" SelectedIndex="{Binding SelectedTab, Converter={c:AppTabToIntConverter}}"
IsEnabled="{Binding IsGuiEnabled}">
<TabItem Header="{x:Static r:Resource.Parameters}">
<view:ParametersView x:Name="parametersView"/>
</TabItem>
<TabItem Header="{x:Static r:Resource.Dates}">
<view:ValidDatesView x:Name="validDatesView"/>
</TabItem>
fill up ViewNameIndexDictionary and IndexViewNameDictionary
Window_Loaded event is too late, AppTabToIntConverter runs before that
public static Dictionary<AppTab, int> ViewNameIndexDictionary { get; set; } = new Dictionary<AppTab, int>();
public static Dictionary<int, AppTab> IndexViewNameDictionary { get; set; } = new Dictionary<int, AppTab>()
private void Window_Initialized(object sender, EventArgs e)
{
var i = 0;
foreach (TabItem item in myTabControl.Items)
{
var tabContentName = ((FrameworkElement)item.Content).Name;
// Convert TabItem name "validDatesView" to "ValidDates"
var appTabString = tabContentName.FirstCharToUpper().CutLastNCharacter("View".Length);
var appTab = (AppTab)Enum.Parse(typeof(AppTab), appTabString);
ViewNameIndexDictionary.Add(appTab, i);
IndexViewNameDictionary.Add(i, appTab);
i++;
}
}
the converter
// The root tag must contain: xmlns:c="clr-namespace:LedgerCommander.ValueConverter"
class AppTabToIntConverter : BaseValueConverter<AppTabToIntConverter>
{
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is AppTab appTab))
throw new Exception("The type of value is not AppTab");
return MainWindowView.ViewNameIndexDictionary[appTab];
}
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is int tabIndex))
throw new Exception("The type of value is not int");
return MainWindowView.IndexViewNameDictionary[tabIndex];
}
}
the BaseValueConverter (thanks to AngelSix)
public abstract class BaseValueConverter<T> : MarkupExtension, IValueConverter where T : class, new()
{
private static T Converter = null;
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Converter ?? (Converter = new T());
}
public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture);
public abstract object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
}

NullToVisibilityConverter not being triggered by selection change

I have a StackPanel that I would like to hide when there isn't a selected item from a ListView in the same window. Currently, when I open the window, there is no selected item and the StackPanel is hidden, but when I do select something from the ListView, no change occurs.
I am binding the SelectedItem in the ListView like:
<ListView
MinHeight="0"
MaxHeight="500"
Margin="10,10,10,0"
Background="#e7f5f4"
BorderThickness="0"
ItemsSource="{Binding Issues}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
SelectedItem="{Binding SelectedIssue}"
SelectionMode="Single">
Where "SelectedIssue" is a custom class property in my ViewModel (my entire window has the same DataContext). I am currently binding the Visibility property of my StackPanel as:
<StackPanel
Grid.Column="1"
Margin="13,0,0,5"
VerticalAlignment="Bottom"
Background="#ebf7f6"
Orientation="Horizontal"
Visibility="{Binding SelectedIssue,
Converter={StaticResource NullToVisibilityConverter},
UpdateSourceTrigger=PropertyChanged}">
And my converter is:
public class NullToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value == null ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
What am I missing?
EDIT: Here is my getter/setter
private Issue _selectedIssue;
public Issue SelectedIssue
{
get { return _selectedIssue; }
set { Set(ref _selectedIssue, value); }
}
public void RaisePropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public bool Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
return false;
storage = value;
RaisePropertyChanged(propertyName);
return true;
}
apparently the problem is that you have not implemented INotifyPropertyChanged or you not raise (in the SelectedIssue property setter) the PropertyChanged Event.
but you can do simpler, binding the StackPanel directly to ListView.SelectedItem:
<ListView x:Name="listView"
MinHeight="0"
MaxHeight="500"
Margin="10,10,10,0"
Background="#e7f5f4"
BorderThickness="0"
ItemsSource="{Binding Issues}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
SelectedItem="{Binding SelectedIssue}"
SelectionMode="Single">
<StackPanel
Grid.Column="1"
Margin="13,0,0,5"
VerticalAlignment="Bottom"
Background="#ebf7f6"
Orientation="Horizontal"
Visibility="{Binding SelectedItem, ElementName=listView
Converter={StaticResource NullToVisibilityConverter}" >

localize language tag to display name converter

I want to convert the localization meta tag e.g. en-US to the display name, in this case English. The meta tag is stored in a ObservableCollection because it will be modified on runtime. I want to bind the display name to a combo box.
ComboBox:
<ComboBox Grid.Column="1" Grid.Row="1" Width="200" VerticalAlignment="Center" HorizontalAlignment="Center" SelectedIndex="0" ItemsSource="{Binding Path=ServerData.AvailableTemplateLanguages}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding, Converter=LanguageTagToNameConverter}" FontSize="12"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Converter:
class LanguageTagToNameConverter : IValueConverter
{
public object Convert(object value,
Type targetType,
object parameter,
CultureInfo culture)
{
return CultureInfo.GetCultureInfo(value.ToString()).DisplayName;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
ObservableCollection:
public class ServerDataObj : ModelBase
{
private ObservableCollection<string> _availableTemplateLanguages = new ObservableCollection<string> { "de-DE", "en-US" };
public ObservableCollection<string> AvailableTemplateLanguages
{
get
{
return _availableTemplateLanguages;
}
set
{
_availableTemplateLanguages = value;
OnPropertyChanged("AvailableTemplateLanguages");
}
}
}
Unfortunately this approach does not work.
You need to put converter into resources:
<Window>
<Window.Resources>
<LanguageTagToNameConverter x:Key="convLang"/>
</Window.Resources>
...
<TextBlock Text="{Binding, Converter={StaticResource convLang}}"/>

How to invoke String.Joing on an ObservableCollection's bound property (to a ListView) that is a List<string>

I have a simple Observable collection with two public properties, int ID and List Targets. The code-behind looks like (simplified code to remove unnecessary and not relevant code):
public class MyClass
{
public ObservableCollection<SomeClass> jobs;
public class SomeClass
{
private int id;
private List<string> targets;
public int ID
{
get { return id; }
set { id = value; }
}
public List<string> Targets
{
get { return targets; }
set { targets = value; }
}
public SomeClass(int _id, List<string> _targets)
{
id = _id;
targets = _targets;
}
}
public MyClass()
{
InitializeComponent();
jobs = new ObservableCollection<SomeClass>();
myListView.ItemsSource = jobs; //jobs is populated from a a loader in Window_Loaded
}
}
The ListView and binding in the xaml look like:
<ListView Name="MyListView" ItemsSource="{Binding Path=jobs, RelativeSource={RelativeSource AncestorType=Window},
Mode=OneWay}" Width="480" Height="155" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,35,10,0" >
<ListView.ContextMenu>
<ContextMenu Name="contextMenuJobRemove">
<ContextMenu.BitmapEffect>
<OuterGlowBitmapEffect />
</ContextMenu.BitmapEffect>
<MenuItem Header="Remove" Click="contextMenuJobRemove_Click" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Parent}"/>
</ContextMenu>
</ListView.ContextMenu>
<ListView.View>
<GridView AllowsColumnReorder="True" ColumnHeaderToolTip="Broadcast call targets">
<GridViewColumn DisplayMemberBinding="{Binding Path=ID}" Header="ID" Width="50" />
<GridViewColumn DisplayMemberBinding="{Binding Path=Targets}" Header="Targets" Width="100" />
</GridView>
</ListView.View>
</ListView>
So when the ListView displays, the Targets columns rightfully displays "(Collection)". Ideally I'd like this column to display something like String.Join(",", Targets.ToArray()). How can this be done, and am I doing this in the xaml or code-behind?
One method would be a converter in the binding.
However this will only update once the property is changed (not the collection contents). So it might be better to expose a display string property in your object and fire change notifications for that property whenever the list changes. If the collection does not change in the first place those notifications are obviously not necessary.
Use a value converter:
[ValueConversion(typeof(IEnumerable<string>), typeof(string))]
public class MyArrayToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return String.Join (",", ( (IEnumerable<string>)value ).ToArray() );
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException ();
}
}
<Window
...
xmlns:my="clr-namespace:XXXXXXXXXXXXXXXXXXXXXCSharpNamespace"
...>
<Window.Resources>
<my:MyArrayToStringConverter x:Key="myconverter" />
</Window.Resources>
...
"{Binding ... Converter={StaticResource myconverter}}"
</Window>
You could create a simple value converter:
public class ListConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
var enumerable = value as IEnumerable;
if (enumerable == null)
return string.Empty;
return String.Join(", ", enumerable.ToArray());
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}

Categories