I am trying to highlight dates with the color "LigthPink" for important dates of the appointments that are scheduled. In my project in WPF MVVM, I created a code, but I cannot update the dates.
I arrived at the following code:
class ConverterHigligthdate: IValueConverter
{
static BindableCollection<DateTime> dict = new BindableCollection<DateTime>();
public event PropertyChangedEventHandler PropertyChanged;
static ConverterHigligthdate()
{
dict.Add(DateTime.Today);
dict.Add(DateTime.Today.AddDays(2));
dict.Add(DateTime.Today.AddDays(-10));
dict.Add(DateTime.Today.AddDays(-20));
dict.Add(DateTime.Today.AddDays(-15));
}
public static void AddDate(DateTime date)
{
dict.Add(date);
}
public static void RemoveDate(DateTime date)
{
dict.Remove(date);
}
public void Clear()
{
dict.Clear();
dict.Refresh();
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string text = null;
if (dict.Contains((DateTime)value))
text = null;
else
text = "";
return text;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And in the view:
<Window.Resources>
<local:ConverterHigligthdate x:Key="ConverterHigligthdate"/>
<Style x:Key="calendarDayButtonStyle" TargetType="{x:Type CalendarDayButton}">
<Setter Property="Margin" Value="8"/>
<Setter Property="FontSize" Value="13"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource ConverterHigligthdate}}" Value="{x:Null}">
<Setter Property="Background" Value="LightPink"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid Margin="5">
<Calendar SelectionMode="MultipleRange" CalendarDayButtonStyle="{DynamicResource calendarDayButtonStyle}"/>
</Grid>
result
Does anyone know how to implement something that makes this work?
You're going about this the wrong way. With MVVM you always do business logic in your view model layer, never in your converters (they're part of the view layer).
There are numerous ways of going about this, but generally you want your view model layer to prepare your data in a format that the view can readily consume. For the purpose of performance, let's wrap all your selected dates in a lookup table:
public class MainViewModel
{
public HashSet<DateTime> Dates { get; } = new HashSet<DateTime>();
public MainViewModel()
{
// highlight today and tomorrow
this.Dates.Add(DateTime.Today);
this.Dates.Add(DateTime.Today.AddDays(1));
}
}
Now in your CalendarDayButtonStyle you want to add a DataTrigger. When the date for the button in question is in your collection, that's when you want to change the background color:
<Style x:Key="CalendarDayButtonStyle" TargetType="CalendarDayButton">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource LookupConverter}">
<Binding />
<Binding Path="DataContext.Dates" RelativeSource="{RelativeSource AncestorType=Calendar}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Pink" />
</DataTrigger>
</Style.Triggers>
</Style>
So all you need now is a converter to do the lookup. We need to pass in both the lookup table as well as the value to look up, so we can use a MultiBinding. This is in fact logic that could have been placed in the view model if we really wanted to, but it doesn't reference any view-model specific data, and it can be re-used elsewhere, so we'll bend the rules a tiny bit:
public class LookupConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var date = (DateTime)values[0];
var dates = values[1] as HashSet<DateTime>;
return dates.Contains(date);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And that's all you need. Result:
Related
I would like to change my image source from:
<Image Source="{svg:SvgImage image.svg}"/>
To something that use binding on an enum property instead:
XAML:
<Resources>
<local:MyConverter x:Key="MyConverter" />
</Resources>
<Image Source="{svg:SvgImage Binding MyEnumProperty, Converter={StaticResource MyConverter}}" />
Code behind:
public enum MyEnum
{
Value1,
Value2
}
public class MyConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var myValue = (MyEnum)(value);
switch (myValue)
{
case MyEnum.Value1:
return "image1.svg";
case MyEnum.Value2:
return "image2.svg";
default:
throw new NotImplementedException();
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
This doesn't work and I suspect that is has something to do with the svg:SvgImage and Binding MyEnumProperty being combined in the same statement.
I get the following errors:
The member "Converter" is not recognized or is not accessible.
And
The property 'Converter' was not found in type 'SvgImageExtension'.
Question:
What is the correct way to do this?
The expression
{svg:SvgImage Binding MyEnumProperty ...}
is not valid XAML, and because SvgImage is a markup extension, you can't bind its properties.
You may however use DataTriggers in an Image Style instead of a Binding with a Converter:
<Image>
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding MyEnumProperty}" Value="Value1">
<Setter Property="Source" Value="{svg:SvgImage image1.svg}"/>
</DataTrigger>
<DataTrigger Binding="{Binding MyEnumProperty}" Value="Value2">
<Setter Property="Source" Value="{svg:SvgImage image2.svg}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
How can change the fill color of an object I'm being to in my mvvm setup using xaml in wpf. I want to change the fill color to red when the attribute being bound to is set to True.
The attribute is called IsRound.
I'll post code if necessary. I'm not on a pc at the moment.
UPDATED
Could someone show an example of how to do this using style triggers?
And set the value based on the bind property bool?
First of all you don't need any Binding for what you are trying to do. DataTrigger is enough. In the example below IsCyan is a boolean property of ViewModel. But Background of TextBlock is not bound at all.
<TextBlock Text="Inside content">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsCyan}" Value="True">
<Setter Property="Background" Value="DarkCyan"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsCyan}" Value="False">
<Setter Property="Background" Value="DarkGoldenrod"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
But if at all you need Binding, solution by the user benPearce to use Converter is the way to go.
You need to use an IValueConverter on the binding.
BackgroundColor="{Binding Path=IsRound, Converter={StaticResource BoolToFillColorConverter}}"
public class BoolToFillColorConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool b;
if (bool.TryParse(value, out b))
{
if (b) return Red
else return Blue;
}
else
{
return SomeDefaultColour;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
If I want to change the Background-color of a Button in wpf to red, if the property Amount in my view model is 0 and to green if it is greater than 0, is it better to use a value converter for this, or should I simply implement a custom Background-property in my view model? This Background-property would wrap the Amount-value to a SolidColorBrush, which will be bound to the Background of the Button.
Which way is more straight forward?
Thank you!
I would use a DataTrigger.
Apply the following style to your button.
It has a binding to the Amount property in your view model.
It sets the default background color to 'green' and changes to 'red' if the value of Amount is 0.
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="Green" />
<Style.Triggers>
<DataTrigger Binding="{Binding Amount}" Value="0">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
Additional info
You can also check for more than one codition using a MultiDataTrigger.
It looks like this:
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{...}, Value="..."/>
<Condition Binding="{...}, Value="..."/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="A" Value="..."/>
<Setter Property="B" Value="..."/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
Check out this article on how to use it.
It seems that for range checking you would need to implement a IValueConverter like mentioned in the other responses or in this answer.
I would do it with Trigger, but Converter is Ok too. But I definitely won't make property Background in ViewModel, because Background is about design, about view so it is better to define it in View
I'd make bool property in viewmodel, which is calculated when Amount is changed:
public bool IsAmountZero
{
get { return Amount == 0; }
}
private int _amount;
public int Amount
{
get { return _amount; }
set
{
_amount = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsAmountZero));
}
}
And then write converter BoolToColorConverter (where colors could be via ConverterParameter somehow).
// in current form it's actually BoolToColorRedGreenConverter
public class BoolToColorConverter : MarkupExtension, IValueConverter
{
public BoolToColorConverter() { }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool)
{
var colorFalse = Colors.Green;
var colorTrue = Colors.Red;
if (parameter != null)
{
//...
}
return (bool)value ? colorTrue : colorFalse;
}
throw new InvalidCastException();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
use it like this
<Button.Background>
<SolidColorBrush Color="{Binding IsAmountZero, Converter={local:BoolToColorConverter}}" />
</Button.Background>
This would be quite reusable solution.
Otherwise just make converter IntZeroCheckToColorGreenRedConverter, but it will not be very reusable compared to one with bool property.
Idea with Brush property in view model is bad, because viewmodel doesn't realy care about colors. Viewmodel should only contain logic related to model which is then used by view. If you want to simply change color (e.g use Blue instead of Green) - this change has to be done in the view. Therefore bool property and BoolToColorConverter (or BoolToSolidBrushConverter to use directly with Background attribute in xaml) converters.
Setting the Button's background is something view's related i don't thing that setting it from the ViewModel is a good idea, i think that it is much better if you define the Amount property in the ViewModel, define a DataTrigger to check the amount value Against the 0 using a Converter
<Window.Resources>
<YurNs:GreaterThanValConverter x:Key="GreaterThanValConverter"/>
</Window.Resources>
<StackPanel>
<TextBox Text="{Binding Amount,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="Button">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="Green"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Amount,Converter={StaticResource GreaterThanValConverter}}" Value="false">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
and the converter
public class GreaterThanValConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int) value > 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
One more thing you may as well consider passing a parameter to the converter to compare against so that your solution would be as customizable as possible.
For different user interfaces I want to show an image depending on a state of a ViewModel object.
For example:
I have a database connection, if connected, I want to show a green database image, if not connected I want to display a red database image.
In the ViewModel there is a bool that represents the state.
Possibilities are:
Having two images in the view (with a converter InverseBooleanToVisibilityConverter for the red image), which are at the same place, actually just showing one of them.
Binding for Image source (but I do not want to set this in my ViewModel)
Some sort of selector?
This state depending image can be more often of use, e.g. in a TreeView as ItemImage.
Is there a more clever way to accomplish that?
You can also do it with solely with data triggers. Here's a sample from one of my projects for a button that changes it's image depending on whether or not the form is in an Edit mode or not:
<Button x:Name="EditOrSaveJob"
Width="32"
Height="32"
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Image>
<Image.Style>
<Style TargetType="Image">
<Setter Property="Source" Value="/AAAA;component/Resources/Images/edit.png" />
<Setter Property="ToolTip" Value="Edit Order" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsEditing}" Value="True">
<Setter Property="Source" Value="/AAAA;component/Resources/Images/32_save.png" />
<Setter Property="ToolTip" Value="Save Order" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Button>
I use a valueconverter like so:
public class BoolToImageConverter: DependencyObject, IValueConverter
{
public BitmapImage TrueImage
{
get { return (BitmapImage)GetValue(TrueImageProperty); }
set { SetValue(TrueImageProperty, value); }
}
public static DependencyProperty TrueImageProperty = DependencyProperty.Register("TrueImage", typeof(BitmapImage), typeof(BoolToImageConverter), null);
public BitmapImage FalseImage
{
get { return (BitmapImage)GetValue(FalseImageProperty); }
set { SetValue(FalseImageProperty, value); }
}
public static DependencyProperty FalseImageProperty = DependencyProperty.Register("FalseImage", typeof(BitmapImage), typeof(BoolToImageConverter), null);
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (bool)value ? TrueImage : FalseImage;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var img = (BitmapImage)value;
return img.UriSource.AbsoluteUri == TrueImage.UriSource.AbsoluteUri;
}
}
and in XAML
<my:BoolToImageConverter x:Key="converterName" FalseImage="{StaticResource falseImage}" TrueImage="{StaticResource trueImage}"/>
The solution would be using converter (class that implements IValueConverter):
class ImageStateConverter : IValueConverter
{
public Object Convert( Object value, Type targetType, Object parameter, CultureInfo culture)
{
bool state = (bool) value;
return state ? "img1.png" : "img2.png";
}
}
Then in your XAML write binding like this:
<Image Source="{Binding Path=State, Converter={StaticResource myConverter}}" />
Object myConverter is declared somewhere in Resources section.
I wrote an application in WPF that has a button and slider. I would like to create a trigger for the button, which would set the button's 'IsEnable' property to false when the slider value is greater than another value.
Right now I have:
<Style x:Key="zoomOutButton" TargetType="Button" BasedOn="{StaticResource ResourceKey=buttonStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentAltitude}" Value="24000">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
But I would like to set isEnable not when the value of CurrentAltitude equal 24000, but when it is equal or greater than 24000.
Any ideas?
You can achieve this using a converter:
public class IsEqualOrGreaterThanConverter : IValueConverter {
public static readonly IValueConverter Instance = new IsEqualOrGreaterThanConverter();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
int intValue = (int) value;
int compareToValue = (int) parameter;
return intValue >= compareToValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
Then your trigger will look like this:
<Style x:Key="zoomOutButton" TargetType="Button" BasedOn="{StaticResource ResourceKey=buttonStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentAltitude, Converter={x:Static my:IsEqualOrGreaterThanConverter.Instance}, ConverterParameter=24000}" Value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
A more generic converter, usable with any comparable type, could be :
public class IsGreaterOrEqualThanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
IComparable v = value as IComparable;
IComparable p = parameter as IComparable;
if (v == null || p == null)
throw new FormatException("to use this converter, value and parameter shall inherit from IComparable");
return (v.CompareTo(p) >= 0);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
But in this case, the ConverterParameter must be interpreted with the same type as the value transmitted to your Converter. For example, to compare an int property 'MyIntProperty' with the contant int value 1, in your XAML, you can use this syntax :
<UserControl x:Class="MyNamespace.MyControl"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:genconverters="clr-namespace:MyConverterNamespace;assembly=MyConvertersAssembly">
<Grid>
<Grid.Resources>
<genconverters:IsGreaterOrEqualThanConverter x:Key="IsEqualOrGreaterThanConverter"/>
<sys:Int32 x:Key="Int1">1</sys:Int32>
</Grid.Resources>
<ComboBox IsEnabled="{Binding MyIntProperty,
Converter={StaticResource IsEqualOrGreaterThanConverter},
ConverterParameter={StaticResource Int1}}"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"/>
</Grid>