WPF DataGrid ClipboardBinding Multibinding possible? - wpf

is there a solution to bind multiple properties to my ClipboardBinding.
I tried the following code but this didnt work:
<DataGridTemplateColumn CanUserSort="True" SortMemberPath="Characteristic.Area.Name.ActualTranslation" MinWidth="120" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="5,0,5,0">
<TextBlock Text="{Binding Characteristic.Area.Name.ActualTranslation}"></TextBlock>
<TextBlock Text=" "></TextBlock>
<TextBlock Text="{Binding AreaItem.Value}"></TextBlock>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{lex:Loc Area}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.ClipboardContentBinding>
<!-- TODO: ClipboardBinding Area -->
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="Characteristic.Area.Name.ActualTranslation" />
<Binding Path="AreaItem.Value" />
</MultiBinding>
</DataGridTemplateColumn.ClipboardContentBinding>
</DataGridTemplateColumn>
i'd appreciate adivce for a workaround too.
Please help

You should use converter (msdn).
class StringFormatConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return string.Format(parameter.ToString(), values);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<DataGridTemplateColumn.ClipboardContentBinding>
<MultiBinding
ConverterParameter=" {0} {1}"
Converter="{StaticResource conString}">
<Binding Path="Characteristic.Area.Name.ActualTranslation" />
<Binding Path="AreaItem.Value" />
</MultiBinding>
</DataGridTemplateColumn.ClipboardContentBinding>

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>

Unable to retrieve actual Width

I am having one list view
<ListView Grid.Row="4" Grid.ColumnSpan="2"
Margin="{Binding ElementName=BorderContainer, Path=Margin}"
FontSize="{DynamicResource PlaceHolderFontSize}"
ItemContainerStyle="{StaticResource ListItemContainerStyle}"
Foreground="Black"
>
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource BorderlessHeader}">
<GridViewColumn Header="Last Backup Date/Time" HeaderTemplate="{StaticResource BlueHeader}"
Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListView}, Converter={StaticResource ColumnWidth},ConverterParameter=221}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Date}" ToolTip="{Binding Date}" FontSize="{DynamicResource ContentFontSize}" Style="{StaticResource GridColumText}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
As mentioned in the code am calculating the header width using converter. and inside my converter am using like below
public object Convert(object value, Type target Type, object parameter, Culture Info culture)
{
ListView listview = value as ListView;
double width = listview.ActualWidth;
but the listview.actualwidth is always giving "0" as result. Why this is happening? any ideas?
Why this is happening?
Probably because the ListView actually has an actual width of 0 by the time the Convert method gets invoked.
any ideas?
You could use a MultiBinding and an IMultiValueConverter that binds to both the ListView itself and its ActualWidth property:
<GridViewColumn Header="Last Backup Date/Time" HeaderTemplate="{StaticResource BlueHeader}">
<GridViewColumn.Width>
<MultiBinding Converter="{StaticResource ColumnWidth}" ConverterParameter="221">
<Binding Path="." RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListView}" />
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListView}" />
</MultiBinding>
</GridViewColumn.Width>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Date}" ToolTip="{Binding Date}" FontSize="{DynamicResource ContentFontSize}" Style="{StaticResource GridColumText}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
public class ColumnWidthConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
ListView listview = values[0] as ListView;
double width = listview.ActualWidth;
if (width == 0)
return (double)parameter;
//...
return 100.0;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Then the converter should be invoked whenever the ActualWidth property is updated.
If you have for example 4 Columns, you can try the following:
XAML
<ListView x:Name="listView">
<ListView.View>
<GridView>
<GridViewColumn Width="{Binding ElementName=listView, Path=ActualWidth, Converter={StaticResource ColumnWidthConverter},ConverterParameter=4}">
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Converter
public class ColumnWidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int columns;
if (int.TryParse(parameter.ToString(), out columns))
{
double width;
if (columns > 0 && double.TryParse(value.ToString(), out width))
{
return width / columns;
}
}
return 100.0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
If you want to be more dynamic, you can write a Behavior and attach it to the ListView. There you can dynamic calculate the values based on the Columns count.

WPF repeater control

I have the following working code:
<StackPanel>
<TextBlock FontSize="14" Foreground="White" Text="Case Type: " TextDecorations="Underline"/>
<RadioButton IsChecked="{Binding CaseType, Converter={StaticResource MyEnumToBooleanConverter}, ConverterParameter={x:Static order:CaseTypeEnum.TypeA}}"
Style="{StaticResource ToggleButtonStyle}"
Content="{Binding CaseType, Converter={StaticResource MyEnumDescriptionConverter}, ConverterParameter={x:Static order:CaseTypeEnum.TypeA}}" />
<RadioButton IsChecked="{Binding CaseType, Converter={StaticResource MyEnumToBooleanConverter}, ConverterParameter={x:Static order:CaseTypeEnum.TypeB}}"
Style="{StaticResource ToggleButtonStyle}"
Content="{Binding CaseType, Converter={StaticResource MyEnumDescriptionConverter}, ConverterParameter={x:Static order:CaseTypeEnum.TypeB}}" />
...
...
...
<RadioButton IsChecked="{Binding CaseType, Converter={StaticResource MyEnumToBooleanConverter}, ConverterParameter={x:Static order:CaseTypeEnum.TypeJ}}"
Style="{StaticResource ToggleButtonStyle}"
Content="{Binding CaseType, Converter={StaticResource MyEnumDescriptionConverter}, ConverterParameter={x:Static order:CaseTypeEnum.TypeJ}}" />
</StackPanel>
Is there any way to do the same functionality without copy/paste :)
Ok without knowing your logic I cannot validate if you actually need two values going into the converter where 1 is the same for every item anyways.
However assuming you indeed do need them:
xaml:
<StackPanel>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.Resources>
<local:MyEnumDescriptionConverter x:Key="MyEnumDescriptionConverter" />
<local:MyEnumToBooleanConverter x:Key="MyEnumToBooleanConverter" />
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton>
<RadioButton.Content>
<MultiBinding Converter="{StaticResource MyEnumDescriptionConverter}">
<Binding Path="." />
<Binding Path="DataContext.CaseType"
RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type ItemsControl}}" />
</MultiBinding>
</RadioButton.Content>
<RadioButton.IsChecked>
<MultiBinding Converter="{StaticResource MyEnumToBooleanConverter}">
<Binding Path="." />
<Binding Path="DataContext.CaseType"
RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type ItemsControl}}" />
</MultiBinding>
</RadioButton.IsChecked>
</RadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
Starting from the Top:
Items is defined as:
public List<CaseTypeEnum> Items {
get {
return Enum.GetValues(typeof(CaseTypeEnum)).Cast<CaseTypeEnum>().ToList();
}
}
and
private CaseTypeEnum _caseType;
public CaseTypeEnum CaseType {
get {
return _caseType;
}
set {
if (value == _caseType)
return;
_caseType = value;
RaisePropertyChanged(() => CaseType);
}
}
enum:
public enum CaseTypeEnum{
TypeA,
TypeB,
TypeC,
TypeD,
TypeE,
TypeF,
TypeG,
TypeH,
TypeI,
TypeJ,
}
As for the two MultiBinding's, I just put some dummy code like
MyEnumDescriptionConverter -
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
if (values.Length < 2)
return string.Empty;
return string.Format("Formatted {0} with CaseType property: {1}", (CaseTypeEnum)values[0], (CaseTypeEnum)values[1]);
}
and MyEnumToBooleanConverter
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
if (values.Length < 2)
return false;
return ((CaseTypeEnum)values[0]).ToString().EndsWith("D");
}
which should when run give you:
You can download the sample Here

Always getting DependencyProperty.unsetvalue

So am testing multibinding in wpf and i have three text boxes which should get the year,month,day and my converter class should return a date with those inputs..pretty simple.
But in my convert method the values[0] is always unset that is i am always getting Dependencyproperty.UnsetValue even if get give it an initial value.
XAML
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:src="clr-namespace:WpfApplication2"
Title="MultiBinding Demo" Width="200" Height="200">
<Window.Resources>
<src:DateConverter x:Key="myConverter" />
</Window.Resources>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
</StackPanel.Resources>
<TextBox Name="tb1" Margin="10" Width="Auto" Height="20"></TextBox>
<TextBox Name="tb2" Margin="10" Width="20" Height="20" ></TextBox>
<TextBox Name="tb3" Width="20" Height="20" ></TextBox>
<Label Name="Date" Width="50" Height="25" Margin="5" >
<Label.Content>
<MultiBinding Converter="{StaticResource myConverter}" Mode="OneWay">
<Binding ElementName="tbl" Path="Text" />
<Binding ElementName="tb2" Path="Text" />
<Binding ElementName="tb3" Path="Text" />
</MultiBinding>
</Label.Content>
</Label>
</StackPanel>
DATECONVERTER CLASS
class DateConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values[0] == DependencyProperty.UnsetValue || values[1] == DependencyProperty.UnsetValue || values[2] == DependencyProperty.UnsetValue)
{
return "";
}
else
{
int year = (int)values[0];
int month = (int)values[1];
int day = (int)values[2];
DateTime date = new DateTime(year, month, day);
return date.ToShortDateString();
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I'm looking at your XAML, and it looks like your first TextBox is named tb1 (number 1) but in the binding you're referencing element name tbl (letter L).

Why is ConvertBack not called on this MultiBinding?

My combobox listing Contacts is bound to both FullName and PhoneExtension using MultiBinding. The Convert method of IMultiValueConverter is called but ConvertBack is not. Why? The combobox properly displays the list but the selection is not saved. It disappears when I tab away.
Background:
1) The Contact list comes from a web service and is put in an observable collection ContactListObservable in the code behind. I'm not using a ViewModel.
PhoneBookService phoneBookService = new PhoneBookService();
PhoneRecordArray pbs = GetCompletePhoneListing();
List<PhoneRecord> pbsList = pbs.PhoneArray.ToList();
ObservableCollection<Contact> observableContacts = new ObservableCollection<Contact>();
foreach(PhoneBookService.PhoneRecord rec in pbsList)
{
Contact c = new Contact();
c.FullName = rec.Person;
c.PhoneExtension = rec.Phone;
observableContacts.Add(c);
}
ContactListObservable = observableContacts;
2) The combobox is in a datagrid located on a UserControl. That's the reason for this wacky binding: ItemsSource="{Binding ContactListObservable, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}"
3) The IMultiValueConverter is a class referenced in UserControl.Resources as <local:CombineNameAndPhoneExtensionMultiConverter x:Key="combinedNameAndPhoneExtensionConverter"/>
4) Legacy data NOT found in the Contacts list must display. This is accomplished with a DataGridTemplateColumn by combining a TextBlock to display values and a ComboBox for editing. See this Julie Lerman MSDN article.
Here is the crazy XAML:
<DataGridTemplateColumn x:Name="DataGridContactTemplateColumn" Header="Contact Using Template">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} Ext. {1}">
<Binding Path="FullName"/>
<Binding Path="PhoneExtension"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate x:Name="ContactsCellEditingTemplate">
<Grid FocusManager.FocusedElement="{Binding ElementName=ContactsTemplateComboBox}">
<ComboBox x:Name="ContactsTemplateComboBox" IsSynchronizedWithCurrentItem="False" IsEditable="False" IsDropDownOpen="True" ItemsSource="{Binding ContactListObservable, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource combinedNameAndPhoneExtensionConverter}">
<Binding Path="FullName" UpdateSourceTrigger="PropertyChanged"/>
<Binding Path="PhoneExtension" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
I've devoted WAY too much time to this so I'll greatly appreciate any help you can offer.
More Background:
The datagrid containing my combobox contains one entity framework contact object per row and includes additional contact fields. Here is some working XAML that succesfully displays and saves FullName but not the phone extension which I want to save in combination with the FullName:
<DataGridTemplateColumn x:Name="DataGridContactTemplateColumn" Header="Contact Using Template">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=FullName}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate x:Name="ContactsCellEditingTemplate">
<Grid FocusManager.FocusedElement="{Binding ElementName=ContactsTemplateComboBox}">
<ComboBox x:Name="ContactsTemplateComboBox" ItemsSource="{Binding ContactListObservable, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" DisplayMemberPath="FullName" SelectedValuePath="FullName" Text="{Binding Path=FullName}" SelectedItem="{Binding Path=FullName}" IsSynchronizedWithCurrentItem="False" IsEditable="False" IsDropDownOpen="True"/>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
The TextBlock will never change it's Text property, so there is no reason to call the ConvertBack method. You would need to be bind to either the ComboBox's SelectedItem or Text properties to get updates.
I'm answering my own question to elaborate on CodeNaked's accurate answer. Add this to the problem XAML and everything works - ConvertBack is called and both FullName and PhoneExtension are saved as needed:
<ComboBox.SelectedItem>
<MultiBinding Converter="{StaticResource combinedNameAndPhoneExtensionConverter}">
<Binding Path="FullName" UpdateSourceTrigger="PropertyChanged"/>
<Binding Path="PhoneExtension" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</ComboBox.SelectedItem>
Here is the combinedNameAndPhoneExtensionConverter code:
public class CombineNameAndPhoneExtensionMultiConverter : IMultiValueConverter
{
public object Convert(object[] values,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
if (values[0] as string != null)
{
string fullName = (string)values[0];
string phoneExtension = (string)values[1];
string namePlusExtension = fullName + ", " + phoneExtension;
return namePlusExtension;
}
return null;
}
public object[] ConvertBack(object value,
Type[] targetTypes,
object parameter,
System.Globalization.CultureInfo culture)
{
Contact c = (Contact)value;
string[] returnValues = { c.FullName, c.PhoneExtension };
return returnValues;
}
}
Thanks CodeNaked for your fast reply!

Resources