I have the following ContentControl:
<ContentControl
Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SelectedEntry}">
<ContentControl.ContentTemplate>
<DataTemplate DataType="controls:HCITextListEntry">
<controls:MyCustomControl
Text="{Binding Text}"
Parameter="{Binding Parameters}"/>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
Everytime the SelectedEntry property changes, I want to redraw/reinit MyCustomControl. Actually only the properties are updated.
You may drop the ContentTemplate and write a converter for the Content Binding that returns a MyCustomControl instance:
<ContentControl Content="{Binding SelectedEntry,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource MyCustomControlConverter}}"/>
The converter:
public class MyCustomControlConverter : IValueConverter
{
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
var control = new MyCustomControl();
control.SetBinding(MyCustomControl.TextProperty,
new Binding("Text") { Source = value });
control.SetBinding(MyCustomControl.ParameterProperty,
new Binding("Parameters") { Source = value });
return control;
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Related
I have created custom control, but TemplateBinding does not work for every property?
Template Binding does start working if I use dummy Converter that just forwards original values.
Simplified example that has the same issue:
<Style TargetType="{x:Type controls:ElipticProgressBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:ElipticProgressBar}">
<ControlTemplate.Resources>
<converters:DebugConverter x:Key="DebugConverter"/>
</ControlTemplate.Resources>
<StackPanel>
<!--BarColor works always-->
<!--BarTickness works-->
<Label Background="{TemplateBinding BarColor}" Content="{TemplateBinding BarTickness}"/>
<!--BarTickness does not works-->
<TextBlock Background="{TemplateBinding BarColor}" Text="{TemplateBinding BarTickness}"/>
<!--BarTickness works-->
<TextBlock Background="{TemplateBinding BarColor}" Text="{Binding BarTickness, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"/>
<!--BarTickness works-->
<TextBlock Background="{TemplateBinding BarColor}" Text="{TemplateBinding BarTickness, Converter={StaticResource DebugConverter}}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
back code:
public class ElipticProgressBar : Control
{
static ElipticProgressBar()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ElipticProgressBar), new FrameworkPropertyMetadata(typeof(ElipticProgressBar)));
}
public static readonly DependencyProperty BarTicknessProperty = DependencyProperty.Register(
"BarTickness", typeof(int), typeof(ElipticProgressBar), new FrameworkPropertyMetadata(default(int)));
public int BarTickness
{
get { return (int)GetValue(BarTicknessProperty); }
set { SetValue(BarTicknessProperty, value); }
}
public static readonly DependencyProperty BarColorProperty = DependencyProperty.Register(
"BarColor", typeof(Brush), typeof(ElipticProgressBar), new FrameworkPropertyMetadata(default(Brush)));
public Brush BarColor
{
get { return (Brush)GetValue(BarColorProperty); }
set { SetValue(BarColorProperty, value); }
}
}
usage:
controls:ElipticProgressBar BarTickness="30" BarColor="Orange"
DebugConverter:
public class DebugConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
{TemplateBinding} is an optimized version of a binding that has some limitations.
In this case you need to change the type of the BarThickness property to string to be able to bind it directly to the Text property of a TextBlock using a {TemplateBinding}.
The docs state that a {TemplateBinding} is
an optimized form of a Binding for template scenarios, analogous to a Binding constructed with {Binding RelativeSource={RelativeSource TemplatedParent}}
with some limitations - most importantly that it works only within the visual tree!
What that means for your specific case: while you cannot use
<TextBlock Text="{TemplateBinding BarTickness}" />
you can (and have to) use
<TextBlock Text="{Binding BarTickness, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}" />
because TextBlock.Text property is of type string while the BarTickness is not.
{TemplateBinding} does not do that kind of converting; that's why you need to use the more generic {Binding} or a converter, whose both methods
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
both convert the actual value to an object.
I have a "Checked ListBox", a ListBox with a CheckBox for each of my Items.
In this ListBox I have a List of Players.
<ListBox ItemsSource="{Binding MyPlayers}" SelectedItem="{Binding SelectedPlayer}" Margin="69,51,347.4,161.8">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem>
<CheckBox IsChecked="{Binding ???}" Content="{Binding Path=Pseudo}" />
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ViewModel :
public class MainWindowViewModel : BaseViewModel
{
SavingContext MyDatabaseContext;
public ObservableCollection<Player> MyPlayers
{
get
{
return MyDatabaseContext.MyPlayers.Local;
}
}
Tournament _MyTournament;
public Tournament MyTournament
{
get
{
return _MyTournament;
}
set
{
_MyTournament = value;
}
}
public MainWindowViewModel(Tournament myTournament)
{
MyDatabaseContext = new SavingContext();
MyDatabaseContext.MyPlayers.Load();
MyTournament = myTournament;
}
}
I pass in my ViewModel a Tournament, that contains an HashSet of Players called Participants.
I would like to bind my IsChecked properties to the result of MyTournament.Participants.Contains(this) with this being the Player related to the CheckBox. But I can't manage to make it works.
Edit :
I tried to use a Converter, but with no success.
<helper:PlayerToTournamentRegistered x:Key="PlayerToTournamentRegistered" />
<ListBoxItem>
<CheckBox IsChecked="{Binding Converter={StaticResource PlayerToTournamentRegistered}}" Content="{Binding Path=Pseudo}" />
</ListBoxItem>
public class PlayerToTournamentRegistered : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//Temp test to see if it would work
if (value == null) return false;
return true;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I get an error each time Provide value on 'System.Windows.Data.Binding' threw an exception
Any Advices ?
So I managed to get it working using a MultiConverter.
The first error that I had was due to the fact that the Mode is by Default Two Way, I only want to have my Converter work OneWay.
I used a MultiBinding to send to my Converter both my ListBoxItem and my Tournament.
My Converter return true or false, and is linked to my CheckBox.IsChecked properties.
List Box :
<ListBox ItemsSource="{Binding MyPlayers}" SelectedItem="{Binding SelectedPlayer}" Margin="69,51,347.4,161.8">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem>
<CheckBox Content="{Binding Path=Pseudo}" >
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource PlayerToTournamentRegistered}" Mode="OneWay">
<Binding />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=Window}" Path="DataContext.MyTournament"/>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Converter :
public class PlayerToTournamentRegistered : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if(!(values[0] is Student) || !(values[1] is Tournament))
{
return false;
}
Tournament myTournament = (Tournament)values[1];
Student myPlayer = (Student)values[0];
if (myTournament.Participants.Contains(myPlayer))
return true;
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
I created a ComboBox listing the colors that System.Windows.Media.Colors predefines, using the approach told in this question: How can I list colors in WPF with XAML?
My XAML code now is:
<Window ...>
<Window.Resources>
<ObjectDataProvider
ObjectInstance="{x:Type Colors}" MethodName="GetProperties" x:Key="ColorList" />
<local:StringToBrushConverter x:Key="FontColorConversions" />
</Window.Resources>
<Grid Background="Black">
...
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource ColorList}}"
SelectedValue="{Binding FontColor, Mode=TwoWay}"
DisplayMemberPath="Name"
SelectedValuePath="Name">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Foreground" Value="{Binding Converter={StaticResource FontColorConversions}}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
...
</Grid>
</Window>
And besides, please note that I bind SelectedValue to a VM class's FontColor property, which is of string type.
class FontSetting : INotifyPropertyChanged
{
private string _fontColor = "Lavender"; // initial color
public event PropertyChangedEventHandler PropertyChanged;
public string FontColor
{
get
{
return _fontColor;
}
set
{
_fontColor = value;
OnPropertyChanged("FontColor");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And I set the DataContext of the Window containing this ComboBox to an instance of FontSetting.
So each item in the ComboBox actually display a string representing a certain color now, what I want to do is set an item's Foreground color to that color its content indicates, like this:
Can anyone help? Thanks.
UPDATED:
Since most of the solutions have a converter which converts string to Brush and actually I already have it, now I want to put mine here, as I binded a TextBox's Foreground to FontSetting's FontColor property, so that when you change the ComboBox, the color of that TextBox changes accordingly.
Here is my converter class, and it works fine by now:
class StringToBrushConverter : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
BrushConverter conv = new BrushConverter();
SolidColorBrush brush = conv.ConvertFromString("Lavender") as SolidColorBrush;
if (null != value)
{
brush = conv.ConvertFromString(value.ToString()) as SolidColorBrush;
}
return brush;
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
When I click the ComboBox to open the dropdown list, I got an exception:
CONCLUSION
Amine's solution works, it's my mistake. I explain briefly now, if you bind a ComboBox to System.Windows.Media.Colors like what I am doing, when the item is rendered, the Convert() method of the converter class (which you assign to the binding) is executed, and actually the value passed to Convert() as its first parameter is a Syetem.Windows.Media.Color instance. I made mistake coz I thought it was of string type.
Therefore, in my case I need two converter classes, one converting string to Brush, and the other one converting Color to Brush. So I will keep my own StringToBrush converter and add Amine's ColorToBrush converter.
However, I simplified Amine's implementation a bit:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
BrushConverter conv = new BrushConverter();
SolidColorBrush brush = SolidColorBrush)conv.ConvertFromString(FontSetting.DEFAULT_FONT_COLOR);
if (null != value)
{
PropertyInfo pi = value as PropertyInfo;
if (null != pi)
{
brush = conv.ConvertFromString(pi.Name) as SolidColorBrush;
}
}
return brush;
}
Moreover, Joe's input is also valuable, put all them together, I can keep the items' color consistent, which is perfect.
You can set Style of ComboBoxItem as bellow :
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240" x:Name="CB"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
DisplayMemberPath="Name"
SelectedValuePath="Name">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Foreground" Value="{Binding Converter={StaticResource converter}}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
By using this converter:
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
object obj = ((System.Reflection.PropertyInfo)value).GetValue(this,null);
return (SolidColorBrush)new BrushConverter().ConvertFromString(obj.ToString());
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return value;
}
}
Two ways, use a value converter or an intermediate property. The easiest is probably an intermediate property as you are using well structured bindings already for your SelectedItem (but value converters are fun too!).
SelectedValue is bound to FontColor, so in your setter set another value:
public string FontColor
{
get
{
return _fontColor;
}
set
{
_fontColor = value;
ForegroundColorToDisplay = GetBrushFromColorString(value);
OnPropertyChanged("FontColor");
}
}
public Brush _foregroundColorToDisplay
public Brush ForegroundColorToDisplay
{
get
{
return _foregroundColorToDisplay;
}
set
{
_foregroundColorToDisplay= value;
OnPropertyChanged("ForegroundColorToDisplay");
}
}
or, if you don't want to store it:
public string FontColor
{
get
{
return _fontColor;
}
set
{
_fontColor = value;
//note it fires two changed events!
OnPropertyChanged("ForegroundColorToDisplay");
OnPropertyChanged("FontColor");
}
}
public Brush ForegroundColorToDisplay
{
get
{
return GetBrushFromColorString(value);;
}
}
You can bind to this new property in your xaml:
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
SelectedValue="{Binding FontColor, Mode=TwoWay}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
Foreground="{Binding ForegroundColorToDisplay, Mode=OneWay}"/>
If you are interested, a value converter would work like this:
Create a class in your code behind (or elsewhere if it's going to be used a lot) that implements IValueConverter, takes the string and returns a Brush:
public class StringToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return GetBrushFromString(value as string)
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
Throw new SomeException();
}
}
and use it in your binding in xaml to convert the selectedItem binding to Foreground brush:
<Window.Resources>
<local:StringToBrushConverter x:Key="converter" />
</Window.Resources>
...
<ComboBox Grid.Column="1" Grid.Row="1" Height="22" Width="240"
VerticalAlignment="Center" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
SelectedValue="{Binding FontColor, Mode=TwoWay}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
Foreground="{Binding FontColor, Mode=OneWay, Converter={StaticResource converter}}"/>
I have a collection of Checkboxes in a Listbox binding to an Enum which uses a converter. Code shown below:
<ListBox HorizontalAlignment="Left" Height="183" Margin="159,30,0,0" VerticalAlignment="Top" Width="144">
<ListBox.Resources>
<local:FlagsEnumValueConverter x:Key="FlagsConverter" />
</ListBox.Resources>
<CheckBox Content="Checkbox1" IsChecked="{
Binding Path=TestModel.TestEnum,
Converter={StaticResource FlagsConverter},
ConverterParameter={x:Static tc:TestEnum.Test1}}"/>
<CheckBox Content="Checkbox2" IsChecked="{
Binding Path=TestModel.TestEnum,
Converter={StaticResource FlagsConverter},
ConverterParameter={x:Static tc:TestEnum.Test2}}"/>
<CheckBox Content="Checkbox3" IsChecked="{
Binding Path=TestModel.TestEnum,
Converter={StaticResource FlagsConverter},
ConverterParameter={x:Static tc:TestEnum.Test3}}"/>
</ListBox>
Converter Code:
class FlagsEnumValueConverter : IValueConverter
{
private int mTargetValue;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var mask = (int)parameter;
mTargetValue = (int)value;
return ((mask & mTargetValue) != 0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
mTargetValue ^= (int)parameter;
return Enum.Parse(targetType, mTargetValue.ToString());
}
}
I'd rather use an ItemTemplate to keep things clean and simple however I'm not sure what to do with the 'ConverterParameter' since it requires the specific Enum value. I've been Googling for a solution but haven't found one. I honestly don't even know where to start. Is this even possible?
I have an object with this structure
public class Parent
{
public string Name {get; set;}
public int ID {get; set;}
public List<Child> Children{set; get;}
public Address HomeAddress {get; set;}
}
I created a hierarchical template for Address, Parent, Children with the ObjectToObservableListConverter
public class ObjectToObservableListConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
return new ObservableCollection<Graphic> { (value as Graphic) };
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
and PropertyToListConverter
public class PropertyToListConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Type type = value.GetType();
PropertyInfo[] propertyList = value.GetType().GetProperties();
List<object> values =
(from property in propertyList
//where property.GetCustomAttributes(typeof(NotForTreeViewAttribute), false).Count() == 0
select property.GetValue(value, BindingFlags.Default, null, null, CultureInfo.InvariantCulture)).ToList();
return values;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The templates I created are:
<TreeView ItemsSource="{Binding Path=Parent,
Converter={StaticResource ResourceKey=objectToListConverter},
UpdateSourceTrigger=PropertyChanged}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type en:Parent}"
ItemsSource="{Binding Path=Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="0,4,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="Children" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type en:Child}"
ItemsSource="{Binding Converter={StaticResource ResourceKey=propertyToListConvertor}}">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="0,4,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding Path=ChildName}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type en:Address}"
ItemsSource="{Binding Converter={StaticResource ResourceKey=propertyToListConvertor}}">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="0,4,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="AddressType" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
If I create the template on Parent
and set the ItemsSource Path of the Children Property, I don't Get the remaining properties of Parent.
Or
If I set the ItemsSource as the property of type Parent and user property to list converter i dont get hierarchy of children.
What am I missing?
Please help. Thanks in Advance
I found the solution to the problem.
I changed my converter to set the list property to another custom class which contains only one property of list type and wrote a hierarchical data template for the same.
PropertyToListConverter
public class PropertyToListConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Type type = value.GetType();
PropertyInfo[] propertyList = value.GetType().GetProperties();
List<object> values =
(from property in propertyList
select GetValueOrElementName(value, property)).ToList();
return values;
}
private static object GetValueOrElementName(object value, PropertyInfo property)
{
var propVal = property.GetValue(value, BindingFlags.Default, null, null, CultureInfo.InvariantCulture);
if (property.PropertyType.IsGenericType && propVal != null)
{
Type type = property.PropertyType.GetGenericArguments().FirstOrDefault();
if (type == typeof(Child))
return new EnumerableClass() { Children = propVal as List<Child> };
}
if (propVal != null && !string.IsNullOrEmpty(propVal.ToString()))
return propVal;
return "Property["+ property.Name+"]";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
CustomClass
public class EnumerableClass
{
public string Name { get; set; }
public List<Child> Children
{
get;
set;
}
}
Hierarchical Data template
<HierarchicalDataTemplate DataType="{x:Type en:Parent}"
ItemsSource="{Binding Converter={StaticResource ResourceKey=propertyToListConvertor}}">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="0,4,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding Path=ParentName}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type ap:EnumerableClass}"
ItemsSource="{Binding Path=Children,UpdateSourceTrigger=PropertyChanged}">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="0,4,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="Children" />
</StackPanel>
</HierarchicalDataTemplate>