Text color depends of value - wpf

First of all, I am new to WPF and Xaml, so I just hope that you understand what I am asking.
I got this situation: There is a listBox of Animals. Every Animal has Weight property. What I am trying to achieve is whenever Weight of Animal is greater then 300 kg, that Weight should be displayed red.

You could use custom converter to achieve that. If your item looks like that:
public class Animal
{
public int Weight { get; set; }
public string Name { get; set; }
}
and ItemTemplate like that:
<DataTemplate x:Key="AnimalTemplate">
<TextBlock Text="{Binding Name}" Foreground="{Binding Weight, Converter={StaticResource AnimalColorSelector}}"/>
</DataTemplate>
Your converter will be like the following one:
public class AnimalColorSelector : IValueConverter
{
private readonly Color _overweightColor = Colors.Red;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is int)
{
return (int) value > 300 ? new SolidColorBrush(_overweightColor) : Binding.DoNothing;
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
This approach has the following pros:
You don't need to hardcode the default color, but inherit it by using Binding.DoNothing.
You don't need to store any style information in a view model.

You could create a ViewModel for Animals that would contain necessary logic for color setting. Like this:
public class VMAnimal : INotifyPropertyChanged
{
private int _weight;
public int Weight
{
get { return _weight; }
set
{
_weight = value;
RaisePropertyChanged("Weight");
RaisePropertyChanged("Color");
}
}
public Brush Foreground
{
get
{
if (Weight > 300)
return new SolidColorBrush(Color.Red);
return new SolidColorBrush(Color.Black);
}
}
}
And use it with binding like this:
<TextBlock Text="{Binding Weight}" Foreground="{Binding Foreground}" />

Related

Get the bound property's PropertyInfo

What I want to achieve is specify specific values for properties that should only be shown in the Designer, but not at runtime.
So in my ViewModels, I want to decorate the properties with a custom attribute
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class DesignTimeValueAttribute : Attribute
{
public object Value { get; }
public DesignTimeValueAttribute(object value)
{
this.Value = value;
}
}
like e.g. this:
private string test;
[DesignTimeValue("Hello World")]
public string Test
{
get { return this.test; }
set
{
if(this.test != value)
{
this.test = value;
this.RaisePropertyChanged();
}
}
}
and in the XAML part, I want to bind to that property like this:
<Window.Resources>
<DesignTimeValueConverter x:Key="DesignTimeValueConverter" />
</Window.Resources>
<Grid>
<TextBox Text="{Binding Test, Converter={StaticResource DesignTimeValueConverter}}" />
</Grid>
So far so good. The DesignTimeValueConverter should look like this (Pseudo Code):
public class DesignTimeValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (DesignerProperties.GetIsInDesignMode(this))
{
PropertyInfo propertyInfo = ...; // What can I put here?
DesignTimeValueAttribute attribute = propertyInfo.GetCustomAttribute<DesignTimeValueAttribute>();
if(attribute != null)
{
return attribute.Value;
}
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
but I don't know if there is a way to fetch the bound properties PropertyInfo.
How can I access the actual property in a IValueConverter, not only the value and its type?
What could I possibly pass as the converter parameter, e.g. could I use
<TextBox Text="{Binding Test, Converter={StaticResource DesignTimeValueConverter}, ConverterParameter=???}" />
and if so, what should I pass?
To fetch the attribute value from DesignTimeValueAttribute, the value converter must use Reflection as follows:
((DisplayAttribute(typeof(className).GetProperty(propertyName).GetCustomAttribute(typeof(DisplayAttribute)))).DesignTimeValue;
To use reflection it needs two things:
name of the class these properties are in
name of the property
You can either add a DependencyProperty for the class name to your value converter or create a MultiValueConverter and pass the name of the class as one of the bindings.
<UserControl.Resources>
<local:DesignTimeValueConverter x:Key="myDesignTimeValueConverter" ClassName="MyNamespace.MyClass" />
</UserControl.Resources>
Then use the converter on the property and also pass the name of the property as the ConverterParameter:
<TextBlock Text="{Binding Test, Converter={StaticResource myDesignTimeValueConverter}, ConverterParameter=Test}" />

Want to display "Yes"/"No" in a dynamically generated column for Boolean value instead of checkbox in RadGridView for WPF

I would like to display "Yes" or "No" for whenever a Boolean type data received(it can receive different types of data) for generating a column in RadGridView instead of a checkbox. I would like to implement this changes in xaml. Columns are generating dynamically. This is how it's created now:
<telerik:RadGridView x:Name="Data" Grid.Row="3" Margin="5" AutoGenerateColumns="False" CanUserSortColumns="True" IsFilteringAllowed="True"
grid:RadGridViewColumnsBinding.ColumnsCollection="{Binding Path=ColumnsData}"
IsReadOnly="False" CanUserResizeColumns="True"/>
I am new in Silverlight coding. Will really appreciate if someone can help.
You should check out Telerik's ConditionalDataTemplateSelector they have in this demo, and read about IValueConverter if you haven't already.
Depending on what you are trying to do with all your columns, the ConditionalDataTemplateSelector might be overkill, but you can use it to create a rule system for what DataTemplate to use for a given cell based on a custom rule system.
<Grid.Resources>
...
<DataTemplate x:Key="CellDisplayTextBox">
<TextBlock Text="{Binding Value, Converter={StaticResource BooleanToYesNoConverter}}" />
</DataTemplate>
<selector:ConditionalDataTemplateSelector x:Key="displaySelector" ConditionConverter="{StaticResource someConverter}">
<selector:ConditionalDataTemplateSelector.Rules>
<selector:ConditionalDataTemplateRule DataTemplate="{StaticResource CellDisplayTextBox}">
<selector:ConditionalDataTemplateRule.Value>
<sys:Int32>1</sys:Int32> <!--You need to figure out what value and type to use here -->
</selector:ConditionalDataTemplateRule.Value>
</selector:ConditionalDataTemplateRule>
...
</selector:ConditionalDataTemplateSelector.Rules>
</Grid.Resources>
...
<telerikGridView:RadGridView>
<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn CellTemplateSelector="{StaticResource displaySelector}" CellEditTemplateSelector="{StaticResource editSelector}" />
</telerik:RadGridView.Columns>
</telerikGridView:RadGridView>
The IValueConverter will let you bind a bool value, but display a string value. For a BooleanToYesNoConverter you could do something like:
public class BooleanToYesNoConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool? bValue = value as bool?;
if (bValue.HasValue)
return bValue.Value ? "Yes" : "No";
else
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
string sValue = value as string;
return sValue == "Yes";
}
}
The ConditionalDataTemplateSelector code from the demo:
public class ConditionalDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
object conditionValue = this.ConditionConverter.Convert(item, null, null, null);
foreach (ConditionalDataTemplateRule rule in this.Rules)
{
if (Equals(rule.Value, conditionValue))
{
return rule.DataTemplate;
}
}
return base.SelectTemplate(item, container);
}
List<ConditionalDataTemplateRule> _Rules;
public List<ConditionalDataTemplateRule> Rules
{
get
{
if (this._Rules == null)
{
this._Rules = new List<ConditionalDataTemplateRule>();
}
return this._Rules;
}
}
IValueConverter _ConditionConverter;
public IValueConverter ConditionConverter
{
get
{
return this._ConditionConverter;
}
set
{
this._ConditionConverter = value;
}
}
}
public class ConditionalDataTemplateRule
{
object _Value;
public object Value
{
get
{
return this._Value;
}
set
{
this._Value = value;
}
}
DataTemplate _DataTemplate;
public DataTemplate DataTemplate
{
get
{
return this._DataTemplate;
}
set
{
this._DataTemplate = value;
}
}
}

Changing element height depending on a selection of combobox

I have a combobox for selecting media types. I would like mediaelement's height to change when .vmw, .mpeg or .avi files are selected. How can I achieve this with MVVM approach?
Thanks in advance
You could bind the Width and Height of the MediaElement directly to its Source property with an appropriate converter, which selects the proper size depending on the media type:
<MediaElement
Width="{Binding Path=Source, RelativeSource={RelativeSource Self}, Converter={StaticResource MediaElementSizeConverter}, ConverterParameter=Width}"
Height="{Binding Path=Source, RelativeSource={RelativeSource Self}, Converter={StaticResource MediaElementSizeConverter}, ConverterParameter=Height}"/>
The converter:
public class MediaElementSizeConverter : IValueConverter
{
private const double defaultWidth = 320d;
private const double defaultHeight = 240d;
private const double wmvWidth = 640d;
private const double wmvHeight = 480d;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Uri source = value as Uri;
if (source != null)
{
if (source.AbsolutePath.EndsWith(".wmv"))
{
return (parameter as string) == "Width" ? wmvWidth : wmvHeight;
}
// more media types ...
}
return (parameter as string) == "Width" ? defaultWidth : defaultHeight;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
One solution would be to bind the ComboBox to a list of self created MediaTypeDefinition classes.
public class MediaTypeDefinition
{
public string Name { get; set; }
public int Height { get; set; }
}
You can then bind the SelectedItem to the height of the media element.
<ComboBox x:Name="mediaTypeList" ItemsSource="{Binding Definitions}" SelectedValuePath="Name" />
<MediaElement Height="{Binding SelectedItem.Height, Elementname=mediaTypeList}" />

Prevent ListBox from focusing but leave ListBoxItem(s) focusable

Here is what happens:
I have a listbox with items. Listbox has focus. Some item (say, 5th) is selected (has a blue background), but has no 'border'.
When I press 'Down' key, the focus moves from ListBox to the first ListBoxItem.
(What I want is to make 6th item selected, regardless of the 'border')
When I navigate using 'Tab', the Listbox never receives the focus again.
But when the collection is emptied and filled again, ListBox itself gets focus, pressing 'Down' moves the focus to the item.
How to prevent ListBox from gaining focus?
P.S.
listBox1.SelectedItem is my own class, I don't know how to make ListBoxItem out of it to .Focus() it.
EDIT: the code
Xaml:
<UserControl.Resources>
<me:BooleanToVisibilityConverter x:Key="visibilityConverter"/>
<me:BooleanToItalicsConverter x:Key="italicsConverter"/>
</UserControl.Resources>
<ListBox x:Name="lbItems">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<ProgressBar HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Visibility="{Binding Path=ShowProgress, Converter={StaticResource visibilityConverter}}"
Maximum="1"
Margin="4,0,0,0"
Value="{Binding Progress}"
/>
<TextBlock Text="{Binding Path=VisualName}"
FontStyle="{Binding Path=IsFinished, Converter={StaticResource italicsConverter}}"
Margin="4"
/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<me:OuterItem Name="Regular Folder" IsFinished="True" Exists="True" IsFolder="True"/>
<me:OuterItem Name="Regular Item" IsFinished="True" Exists="True"/>
<me:OuterItem Name="Yet to be created" IsFinished="False" Exists="False"/>
<me:OuterItem Name="Just created" IsFinished="False" Exists="True"/>
<me:OuterItem Name="In progress" IsFinished="False" Exists="True" Progress="0.7"/>
</ListBox>
where OuterItem is:
public class OuterItem : IOuterItem
{
public Guid Id { get; set; }
public string Name { get; set; }
public bool IsFolder { get; set; }
public bool IsFinished { get; set; }
public bool Exists { get; set; }
public double Progress { get; set; }
/// Code below is of lesser importance, but anyway
///
#region Visualization helper properties
public bool ShowProgress
{
get
{
return !IsFinished && Exists;
}
}
public string VisualName
{
get
{
return IsFolder ? "[ " + Name + " ]" : Name;
}
}
#endregion
public override string ToString()
{
if (IsFinished)
return Name;
if (!Exists)
return " ??? " + Name;
return Progress.ToString("0.000 ") + Name;
}
public static OuterItem Get(IOuterItem item)
{
return new OuterItem()
{
Id = item.Id,
Name = item.Name,
IsFolder = item.IsFolder,
IsFinished = item.IsFinished,
Exists = item.Exists,
Progress = item.Progress
};
}
}
Сonverters are:
/// Are of lesser importance too (for understanding), but will be useful if you copy-paste to get it working
public class BooleanToItalicsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool normal = (bool)value;
return normal ? FontStyles.Normal : FontStyles.Italic;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool exists = (bool)value;
return exists ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
But most important, is that UserControl.Loaded() has:
lbItems.Items.Clear();
lbItems.ItemsSource = fsItems;
where fsItems is ObservableCollection<OuterItem>.
The usability problem I describe takes place when I Clear() that collection (fsItems) and fill with new items.
Please provide your code. Usually the cause of this problem lies in ContentPresenters and KeyboardNavigation.IsTabStop property. But sometimes it's not. So the code would help.
The answer to your question may depend on the way your listbox is getting focus. Here is the solution if you are using an access key (ex: alt+c). You have to implement your own listbox control and override the OnAccessKey method. If this is not your scenario, then I would suggest looking into the OnIsKeyboardFocusWithinChanged method. Try using the same approach I did in the code below.
protected override void OnAccessKey(System.Windows.Input.AccessKeyEventArgs e)
{
if (SelectedIndex >= 0)
{
UIElement element = ItemContainerGenerator.ContainerFromIndex(SelectedIndex) as UIElement;
if (element != null)
{
element.Focus();
}
}
}

WPF binding ComboBox to enum (with a twist)

Well the problem is that I have this enum, BUT I don't want the combobox to show the values of the enum. This is the enum:
public enum Mode
{
[Description("Display active only")]
Active,
[Description("Display selected only")]
Selected,
[Description("Display active and selected")]
ActiveAndSelected
}
So in the ComboBox instead of displaying Active, Selected or ActiveAndSelected, I want to display the DescriptionProperty for each value of the enum. I do have an extension method called GetDescription() for the enum:
public static string GetDescription(this Enum enumObj)
{
FieldInfo fieldInfo =
enumObj.GetType().GetField(enumObj.ToString());
object[] attribArray = fieldInfo.GetCustomAttributes(false);
if (attribArray.Length == 0)
{
return enumObj.ToString();
}
else
{
DescriptionAttribute attrib =
attribArray[0] as DescriptionAttribute;
return attrib.Description;
}
}
So is there a way I can bind the enum to the ComboBox AND show it's content with the GetDescription extension method?
Thanks!
I would suggest a DataTemplate and a ValueConverter. That will let you customize the way it's displayed, but you would still be able to read the combobox's SelectedItem property and get the actual enum value.
ValueConverters require a lot of boilerplate code, but there's nothing too complicated here. First you create the ValueConverter class:
public class ModeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
return ((Mode) value).GetDescription();
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotSupportedException();
}
}
Since you're only converting enum values to strings (for display), you don't need ConvertBack -- that's just for two-way binding scenarios.
Then you put an instance of the ValueConverter into your resources, with something like this:
<Window ... xmlns:WpfApplication1="clr-namespace:WpfApplication1">
<Window.Resources>
<WpfApplication1:ModeConverter x:Key="modeConverter"/>
</Window.Resources>
....
</Window>
Then you're ready to give the ComboBox a DisplayTemplate that formats its items using the ModeConverter:
<ComboBox Name="comboBox" ...>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource modeConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
To test this, I threw in a Label too, that would show me the actual SelectedItem value, and it did indeed show that SelectedItem is the enum instead of the display text, which is what I would want:
<Label Content="{Binding ElementName=comboBox, Path=SelectedItem}"/>
I like the way you think. But GetCustomAttributes uses reflection. What is that going to do to your performance?
Check out this post:
WPF - Displaying enums in ComboBox control
http://www.infosysblogs.com/microsoft/2008/09/wpf_displaying_enums_in_combob.html
This is how I am doing it with MVVM. On my model I would have defined my enum:
public enum VelocityUnitOfMeasure
{
[Description("Miles per Hour")]
MilesPerHour,
[Description("Kilometers per Hour")]
KilometersPerHour
}
On my ViewModel I expose a property that provides possible selections as string as well as a property to get/set the model's value. This is useful if we don't want to use every enum value in the type:
//UI Helper
public IEnumerable<string> VelocityUnitOfMeasureSelections
{
get
{
var units = new []
{
VelocityUnitOfMeasure.MilesPerHour.Description(),
VelocityUnitOfMeasure.KilometersPerHour.Description()
};
return units;
}
}
//VM property
public VelocityUnitOfMeasure UnitOfMeasure
{
get { return model.UnitOfMeasure; }
set { model.UnitOfMeasure = value; }
}
Furthermore, I use a generic EnumDescriptionCoverter:
public class EnumDescriptionConverter : IValueConverter
{
//From Binding Source
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is Enum)) throw new ArgumentException("Value is not an Enum");
return (value as Enum).Description();
}
//From Binding Target
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is string)) throw new ArgumentException("Value is not a string");
foreach(var item in Enum.GetValues(targetType))
{
var asString = (item as Enum).Description();
if (asString == (string) value)
{
return item;
}
}
throw new ArgumentException("Unable to match string to Enum description");
}
}
And finally, with the view I can do the following:
<Window.Resources>
<ValueConverters:EnumDescriptionConverter x:Key="enumDescriptionConverter" />
</Window.Resources>
...
<ComboBox SelectedItem="{Binding UnitOfMeasure, Converter={StaticResource enumDescriptionConverter}}"
ItemsSource="{Binding VelocityUnitOfMeasureSelections, Mode=OneWay}" />
I suggest you use a markup extension I had already posted here, with just a little modification :
[MarkupExtensionReturnType(typeof(IEnumerable))]
public class EnumValuesExtension : MarkupExtension
{
public EnumValuesExtension()
{
}
public EnumValuesExtension(Type enumType)
{
this.EnumType = enumType;
}
[ConstructorArgument("enumType")]
public Type EnumType { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (this.EnumType == null)
throw new ArgumentException("The enum type is not set");
return Enum.GetValues(this.EnumType).Select(o => GetDescription(o));
}
}
You can then use it like that :
<ComboBox ItemsSource="{local:EnumValues local:Mode}"/>
EDIT: the method I suggested will bind to a list of string, which is not desirable since we want the SelectedItem to be of type Mode. It would be better to remove the .Select(...) part, and use a binding with a custom converter in the ItemTemplate.
Questions of using reflection and attributes aside, there are a few ways you could do this, but I think the best way is to just create a little view model class that wraps the enumeration value:
public class ModeViewModel : ViewModel
{
private readonly Mode _mode;
public ModeViewModel(Mode mode)
{
...
}
public Mode Mode
{
get { ... }
}
public string Description
{
get { return _mode.GetDescription(); }
}
}
Alternatively, you could look into using ObjectDataProvider.
I've done it like this :
<ComboBox x:Name="CurrencyCodeComboBox" Grid.Column="4" DisplayMemberPath="." HorizontalAlignment="Left" Height="22" Margin="11,6.2,0,10.2" VerticalAlignment="Center" Width="81" Grid.Row="1" SelectedValue="{Binding currencyCode}" >
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
in code I set itemSource :
CurrencyCodeComboBox.ItemsSource = [Enum].GetValues(GetType(currencyCode))

Resources