ComboBox KeyValuePair Binding WPF - Display Member - wpf

i got a quick question about binding the DisplayMember of my ComboBox.
I have a list with a KeyValuePair for example:
1, Value1;
2, Value2;
3, Value3;
My SelectedValuePath is set to Key which is in my example "1".
Now i want my DisplayMemberPath to show "Key - Value" so for example the Textbox should show "1 - Value1".
Is that possible?
Thank's in advance!

If your ComboBox is not editable, you can create a DataTemplate for your key-value pairs.
<ComboBox ...>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Key, Mode=OneWay}"/>
<Run Text=" - "/>
<Run Text="{Binding Value, Mode=OneWay}"/>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

You can do for instance so:
<ComboBox x:Name="cmb1" ItemsSource="{Binding YourDictionary}" SelectedValuePath="Key">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Key}"/>
<TextBlock Text="-"/>
<TextBlock Text="{Binding Value}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBox Text="{Binding SelectedValue, ElementName=cmb1}"/>

One more way you can go is to use value converter:
<ComboBox x:Name="cmb1" ItemsSource="{Binding YourDictionary}" SelectedValuePath="Key">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource YourConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBox Text="{Binding SelectedValue, ElementName=cmb1}"/>
public class KeyValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is KeyValuePair<int, object> obj)//use your types here
{
return obj.Key.ToString() + "-" + obj.Value.ToString();
}
return value;
}
public object ConvertBack(object value, Type targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException("One way converter.");
}
}

Related

WPFLocalizationExtension with ItemTemplate

I'm using WPFLocalizationExtension for my WPF app.
I have one ComboBox for languages selection. Item source is an ObservableCollection<KeyValuePair<string, string>> as below:
TITLE_LANGUAGE_ENGLISH : en
TITLE_LANGUAGE_VIETNAMESE: vi-VN
This is my xaml code:
<TextBlock Text="{lex:Loc TITLE_LANGUAGE}"></TextBlock>
<ComboBox Grid.Column="1"
ItemsSource="{Binding AvailableLanguages}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{lex:Loc Key={Binding Key}}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
When I run the application, it throws me an exeption as below:
A 'Binding' cannot be set on the 'Key' property of type 'LocExtension'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject
How can I translate the ItemTemplate ?
Thank you,
You could use an IMultiValueConverter together with a MultiBinding, so that you don't loose the ability to update the localization on-the-fly.
<ComboBox ItemsSource="{Binding AvailableLanguages}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding>
<MultiBinding.Bindings>
<Binding Path="Key" Mode="OneTime"/>
<Binding Path="Culture" Source="{x:Static lex:LocalizeDictionary.Instance}"/>
</MultiBinding.Bindings>
<MultiBinding.Converter>
<l:TranslateMultiConverter/>
</MultiBinding.Converter>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
And here is the converter:
class TranslateMultiConverter : DependencyObject, IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length == 2)
{
var key = values[0] as string;
if (key == null)
{
return DependencyProperty.UnsetValue;
}
var cultureInfo = (values[1] as CultureInfo) ?? culture;
return LocalizeDictionary.Instance.GetLocalizedObject(key, this, cultureInfo);
}
return values.FirstOrDefault();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The LocalizeDictionary will raise a PropertyChanged event when the app's language will change causing the MultiBinding to refresh the values.
Note that the converter is a DependencyObject too. This is to provide the context to the LocalizeDictionary when calling the GetLocalizedObject method.
you have to bind to the Path Key directly. The TextBlock at DataTemplate points directly to a single KeyValuePair object, that you can access the property Key directly.
<TextBlock Text="{lex:Loc TITLE_LANGUAGE}"></TextBlock>
<ComboBox Grid.Column="1"
ItemsSource="{Binding AvailableLanguages}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Key}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Update:
Maybe you have to add a Converter. Try WPFLocalizeExtension.TypeConverters.DefaultConverter or implement a class deriving from IValueConverter by yourself.
<ComboBox.Resources>
<cv:DefaultConverter x:Key="DConv" />
</ComboBox.Resources>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Key, Converter={StaticResource DConv}}" />
</DataTemplate>
</ComboBox.ItemTemplate>

How to keep the selected date in the DatePicker in DataGrid?

I have a problem with the DatePicker in a DataGrid. When I click the first time in a cell DatePicker displays the current date me, but when I click next cell DataGridTextColumn date changes to the default 01-01-0001. How to keep the selected date in the DatePicker in DataGrid?
<!--------------------------------------xaml------------------------------------------->
<DataGridTemplateColumn Header="Data" Width="70">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataZdarzenia, StringFormat='dd-MM-yyyy', Converter={StaticResource DateConverter}}" HorizontalAlignment="Center"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker x:Name="datePicker" SelectedDate="{x:Static sys:DateTime.Now}" FirstDayOfWeek="Monday"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<!--------------------------------------xaml------------------------------------------->
This is the correct way
Create a DataConverter Class
public class DateConverter : System.Windows.Data.IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
DateTime date = (DateTime)value;
if (date != null && date.Year != 1)
{
return date;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return DateTime.MinValue;
DateTime val;
if (value.ToString() == " ")
return DateTime.MinValue;
if (DateTime.TryParse(value.ToString(), out val))
return val;
else
return DateTime.MinValue;
}
}
in the XAML add this code for resource
xmlns:l="clr-namespace:Progject.Control.Converter.WPF"
<UserControl.Resources>
<l:DateConverter x:Key="dateConverter"/>
</UserControl.Resources>
and in the section grid when you have a datepiker insert this code:
<DataGridTemplateColumn SortMemberPath="StartDate" ClipboardContentBinding="{Binding StartDate}" Header="Start date" IsReadOnly="False">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding Path=StartDate,Mode=TwoWay,Converter={StaticResource dateConverter}}" VerticalAlignment="Center" HorizontalAlignment="Left">
</DatePicker>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=StartDate,Mode=TwoWay,StringFormat={}{0:dd/MM/yyyy},Converter={StaticResource dateConverter}}" VerticalAlignment="Center" HorizontalAlignment="Left">
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Your Binding should be TwoWay
<TextBlock Text="{Binding DataZdarzenia, Mode=TwoWay, StringFormat='dd-MM-yyyy', Converter={StaticResource DateConverter}}" HorizontalAlignment="Center"/>

Embed DataGrid into WPF Treeview nodes

I need to display Hierarchy in the treeview. But Details should be displayed in the datagrid.
How do I have to write my template to achieve this? I misunderstand smth in templates for now.
<TreeView ItemsSource="{Binding Path=Categories}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type stackProjects:Category}" ItemsSource="{Binding Path=SubCategories}">
<TextBlock Margin="3" Text="{Binding Path=CategoryName}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type stackProjects:SubCategory}" ItemsSource="{Binding Path=Details}">
<TextBlock Text="{Binding Path=SubCategoryName}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type stackProjects:Detail}" >
<StackPanel Orientation="Horizontal">
<TextBlock Margin="3" Text="{Binding Path=Name}"/>
<TextBlock Margin="3" Text=" - "/>
<TextBlock Margin="3" Text="{Binding Path=Info}"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
I've found a workaround. I had to understand that Details should be presented as a collection within a single element which has IEnumerable property. May be it's not the best solution but it works.
I needed to create a Converter to wrap my collection into single one.
public class BoxingItemsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var values = value as IEnumerable;
var type = parameter as Type;
if (values == null || type == null)
return null;
if (type.GetInterfaces().Any(x => x == typeof (IItemsWrapper)))
{
var instance = (IItemsWrapper) type.Assembly.CreateInstance(type.FullName);
instance.Items = (IEnumerable) value;
//returned value should be IEnumerable with one element.
//Otherwise we will not see children nodes
return new List<IItemsWrapper> {instance};
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Example for wrappers:
internal interface IItemsWrapper
{
IEnumerable Items { get; set; }
}
public class ItemsWrapper : IItemsWrapper
{
public IEnumerable Items { get; set; }
}
public class DetailItemsWrapper : ItemsWrapper{}
public class InvoiceItemsWrapper:ItemsWrapper{}
And the xaml. It will not require a lot of changes. You just need to use Boxing converter and set the Type to return in the converter parameter.
<TreeView ItemsSource="{Binding Path=Categories}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type wpfProj:Category}" ItemsSource="{Binding Path=SubCategories}">
<TextBlock Margin="4" Text="{Binding Path=CategoryName}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type wpfProj:SubCategory}"
ItemsSource="{Binding Path=Details, Converter={StaticResource boxingItems}, ConverterParameter={x:Type wpfProj:DetailItemsWrapper}}" >
<StackPanel>
<TextBlock Margin="4" Text="{Binding Path=SubCategoryName}"/>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type wpfProj:DetailItemsWrapper}" >
<DataGrid ItemsSource="{Binding Path=Items}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
I've uploaded sample to dropbox.
Here is how it does look like:

How do I Bind 2 controls to 1 field AND also access the 2 control values for ConvertBack?

In a DataGridTemplateColumn DataTemplate,
I want to bind 2 controls to a string field of format "[name]:[value]" i.e. the string is delimited by colon ":". I need to bind control a) to the [name] part and control b) the value part.
I have been able to successfully use an IValueConverter to split the string for display:
public class NameAndValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string rtn = "";
string[] split = value.ToString().Split(':');
if (split.Count() == 2)
{
if(parameter.ToString() == "Name")
rtn = split[0];
if(parameter.ToString() == "Value")
rtn = split[1];
}
return rtn;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException("NameAndValueConverter can only be used OneWay.");
}
}
And the XAML:
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<local:NameAndValueConverter x:Key="NameAndValueConverter" />
</StackPanel.Resources>
<TextBox x:Name="namePart" Text="{Binding Path=FieldType, Converter={StaticResource NameAndValueConverter}, ConverterParameter='Name'}" />
<TextBox x:Name="valuePart" Text="{Binding Path=FieldType, Converter={StaticResource NameAndValueConverter}, ConverterParameter='Value'}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
But the data may be edited in the Textboxes so how can I access the 2 TextBox values in ConvertBack so that they can be joined again?
Doing this in the XAML:
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<local:NameAndValueConverter x:Key="NameAndValueConverter" />
</StackPanel.Resources>
<TextBox x:Name="namePart" Text="{Binding Path=FieldType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource NameAndValueConverter}, ConverterParameter='Name'}" />
<TextBox x:Name="valuePart" Text="{Binding Path=FieldType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource NameAndValueConverter}, ConverterParameter='Value'}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
should be enough. You need a TwoWay binding to edit from UI, and when you set UpdateSourceTrigger to PropertyChanged the TextBox will update itself automatically when the property will be modified in the ViewModel (you'd obviously need to implement INotifyPropertyChanged )

Display sum of grouped items in ListView

I'm creating a WPF TimeCard app using the MVVM design pattern, and I'm trying to display the sum (total) hours the user has clocked in grouped by each day. I have a ListView with all of the TimeCard data broken into groups using the following XAML:
<ListView.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupItemStyle}">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name, StringFormat=\{0:D\}}" FontWeight="Bold"/>
<TextBlock Text=" (" FontWeight="Bold"/>
<!-- This needs to display the sum of the hours -->
<TextBlock Text="{Binding ???}" FontWeight="Bold"/>
<TextBlock Text=" hours)" FontWeight="Bold"/>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
Is this even possible? At first I thought I would create a partial class of the CollectionViewGroup and add my own properties. But I'm not sure this will even work. Perhaps there is a better solution... any suggestions?
To expand on what e.tadeu said, you can bind your HeaderTemplate's DataTemplate to the Items property of the CollectionViewGroup. This will return you all of the items that are in the current group.
Then you can supply a converter that will return you the desired data from that collection of items. In your case, you say you want the sum of the hours. You could implement a converter that does something like:
public class GroupHoursConverter : IValueConverter
{
public object Convert(object value, System.Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
if (null == value)
return "null";
ReadOnlyObservableCollection<object> items =
(ReadOnlyObservableCollection<object>)value;
var hours = (from i in items
select ((TimeCard)i).Hours).Sum();
return "Total Hours: " + hours.ToString();
}
public object ConvertBack(object value, System.Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
throw new System.NotImplementedException();
}
}
Then you could use this converter on your data template:
<Window.Resources>
<local:GroupHoursConverter x:Key="myConverter" />
</Window.Resources>
<ListView.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupItemStyle}">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name,
StringFormat=\{0:D\}}"
FontWeight="Bold"/>
<TextBlock Text=" (" FontWeight="Bold"/>
<!-- This needs to display the sum of the hours -->
<TextBlock Text="{Binding Path=Items,
Converter={StaticResource myConverter}}"
FontWeight="Bold"/>
<TextBlock Text=" hours)" FontWeight="Bold"/>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
Cheers!
Use a Converter.
See:
http://www.codeproject.com/KB/WPF/WPFAggregateConverter.aspx
This might help you too:
http://www.codeproject.com/KB/WPF/DsxGridCtrl.aspx
Just use "ItemCount" in GroupStyle to show current count of containing items, per this tutorial on ListView grouping.

Resources