Using MarkupExtension to display images in xaml - wpf

I have a Resource (.resx) with a log of svg images in it. Furthermore I use an ImageResource class that can take the byte[] provided by the resource and convert it into an ImageSource Object.
While doing that, I have the possibility to Change the color of the Icon
ImageResource.CreateFromSvg(image, Brushes.Green);
I was searching for an easy way to get these Images into xaml, so I created another static class that creates an Image using the ImageResource class. In xaml I could use
<Image Source={x:Static img:ImageResources.MyGreenIcon}/>
to get the icon. The downside was, that I was not able to choose the color in xaml. So I tried and created a MarkupExtension for that. It takes a byte[] and a Brush as parameters, calls the CreateFromSvg-Method and returns the Image. In Xaml it looks like this
<Image Source={i:Img {x:Static img:SvgResources.TheIcon}, Green}/>
Where SvgResources is the .resx-file the Image is in.
Although this works well, I was not able to change the color at runtime. Because MarkupExtension is not a DependencyProperty I can not use a binding for the color. I tried to use a trigger
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Source" Value="{i:Img {x:Static img:SvgResources.TheIcon}, Blue}" />
</Trigger>
</Style.Triggers>
</Style>
This does not give any error, but just do nothing. So my questions are
How can I reassign the Source of the Image to get another color
Is there a way to use something like a binding to change the color from VM
Is there a way to shorten the {x:Static img:SvgResources.TheIcon to something like svgResource.TheIcon
Edit
I found out, that the Trigger does change the Source if I don't set the source in the control itself, but instead provide a default value in the trigger (IsMouseOver = false). Questions 2 + 3 still remain

Answer to question 2.
You can extend your MarkupExtension to support Binding with MultiValueConverter.
Create simple MultiValueConverter, which will simply call your ImageResource.CreateFromSvg(...);. Values passed to this converter will be image and Brush (static image or brush resolved from Bindings)
In ProvideValue(...) detect is passed value Brush or Binding. If it's Brush create mock Binding for it with this Brush as Source.
If value is Binding - we will use it. Do same for image
Create Multibinding, assign just created MultiValueConverter and add image and brush bindings.
return provided value.
Complete example of Img MarkupExtension:
public class Img : MarkupExtension
{
private readonly object _image;
private readonly object _brush;
public Img(object image, object brush)
{
_image = image;
_brush = brush;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var imageBinding = _image is Binding iBinding ? iBinding : new Binding { Source = _image};
var brushBinding = _brush is Binding bBinding ? bBinding : new Binding { Source = _brush };
var binding = new MultiBinding
{
Converter = new ImgValueConverter()
};
binding.Bindings.Add(imageBinding);
binding.Bindings.Add(brushBinding);
return binding.ProvideValue(serviceProvider);
}
private class ImgValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var image = values[0];
var brush = values[1];
return ImageResource.CreateFromSvg(image, brush);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Usage example:
<Image Source="{i:Img {x:Static i:SvgResources.TheIcon}, {x:Static Brushes.Yellow}}"/>
<Image Source="{i:Img {x:Static i:SvgResources.TheIcon}, {Binding Brush}}"/>

Related

WPF ListBox - Change background color of items programmatically

I have something like this:
public class Member {
public int Id { get; set; }
public string Name { get; set; }
public string Age { get; set; }
...
}
List<Member> members = new List<Member>{ new Member(Id = 1, Name="Chuck", Age="32"), new Member(Id = 2, Name="Eve", Age="10")};
Listbox1.ItemsSource = members;
How can I change the background of items in the ListBox, if the age is less than 18?
Setting the background of a ListBoxItem can be done by changing the item container style. Conditional setting of the color however requires a IValueConverter. You can write your own for checking Age. Please consider making Age a property of type int, otherwise you will have to parse it on each conversion from string.
public class AgeToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return int.Parse((string)value) < 18 ? new SolidColorBrush(Colors.Red) : new SolidColorBrush(Colors.Blue);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException("This is a one-way conversion.");
}
}
XAML solution
In XAML you would create an instance of your converter in the resouces of the list box or any other resource dictionary. Then you would bind the ItemsSource to your members property and add a custom items container style. The style is based on the default style for the ListBoxItem. In it you bind the Background property to Age with your custom converter, which will convert the age string to a solid color brush.
<ListBox x:Name="Listbox1" ItemsSource="{Binding members}">
<ListBox.Resources>
<local:AgeToColorConverter x:Key="AgeToColorConverter"/>
</ListBox.Resources>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="Background" Value="{Binding Age, Converter={StaticResource AgeToColorConverter}}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Code-Behind solution
Suppose you defined your list box in XAML like this.
<ListBox x:Name="Listbox1"/>
Then the same as for the XAML solution applies to this code, except for we create the converter as a local variable and the items source is directly assigned instead of a binding.
var ageToColorConverter = new AgeToColorConverter();
var baseItemContainerStyle = (Style)FindResource(typeof(ListBoxItem));
var itemContainerStyle = new Style(typeof(ListBoxItem), baseItemContainerStyle);
var backgroundSetter = new Setter(BackgroundProperty, new Binding("Age") { Converter = ageToColorConverter });
itemContainerStyle.Setters.Add(backgroundSetter);
Listbox1.ItemContainerStyle = itemContainerStyle;
Listbox1.ItemsSource = members;

Is it possible to set "x:Class" more than one into a ResourceDictionary file?

I have a ResourceDictionary file and also I have two classes to use for it. One of them is IValueConverter and the other is for the EventHandlers related to the control. The class name is EventHandlers set as x:Class attribute value. I also need to set Converters as a second x:Class. But I can't do it because the designer throws an error that says x:Class is set more than one time. How can I solve this issue?
Converters.cs
class Converters : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double pr = (double)value;
AltoProgressBar bar = parameter as AltoProgressBar;
return pr * bar.Width / bar.Maximum;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
EventHandlers.cs
public partial class EventHandlers
{
private void progressBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
ProgressBar progressBar = sender as ProgressBar;
var template = progressBar.Template;
//Find the Rectangle in the ControlTemplate
var layer = (Rectangle)(template.FindName("rect", progressBar));
//Calculate the increment amount depending maxValue and Width
double artis = progressBar.Value * progressBar.Width / progressBar.Maximum;
DoubleAnimation anim = new DoubleAnimation(toValue: artis, duration: TimeSpan.FromMilliseconds(100));
layer.BeginAnimation(Rectangle.WidthProperty, anim);
}
}
styles.xaml
<ResourceDictionary xmlns:my="clr-namespace:AltoSS"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="AltoSS.Converters"
<!--this doesn't make any sense-->
x:Class="AltoSS.EventHandlers">
<!--All styles in here-->
</ResourceDictionary>
I think thats not possible to set the x:Class-Attribute multiple times (polymorphism aims).
If you only want to use your class Converters (more specific name would be nicer) and your EventHandeler you need to define the namespaces of both classes in RD-Tag (similar to xmlns:YourNamespace=clr-namespace:YourProject.NamespaceName).
Then you can define your Converters with x:Key as Static Resource.
like this
<ResourceDictionary xmlns:my="clr-namespace:AltoSS"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:AltoConverters="AltoSS.NamespaceConverters"
xmlns:AltoEventHandlers="AltoSS.NamespaceEventHandlers">
<!--NamespaceConverters and NamespaceEventHandlers from your cs files -->
<!-- for use as static Resource -->
<AltoConverters:Converters x:Key="YourConverters" />
<!-- example -->
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding ...Path..., Converter={StaticResource YourConverters}" />
</Style>
</ResourceDictionary>

Colouring a hierarchical XamDataGrid

I'm using a XamDataGrid (Infragistics-control) to display some hierarchical data. The objects that I can have up to 10 levels and I need to be able to give each level a specific background-color. I use the AssigningFieldLayoutToItem-event to get the "level" of the item and it would be best to assign the background/style here as well, I suppose.
I have tried specifying a DataRecordCellArea-style and even a CellValuePresenter-style but I can't get any of these to work with the FieldLayouts.
Another solution is to write a FieldLayout for each level, but this would create a lot of unnecessary XAML-code.
Any suggestions as to what I should do?
If you have a different FieldLayout for each level, you could use a single style targeting the DataRecordPresenter with a converter to set the background.
XAML:
<local:BackgroundConverter x:Key="BackgroundConverter"/>
<Style TargetType="{x:Type igDP:DataRecordPresenter}">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=FieldLayout.Key, Converter={StaticResource BackgroundConverter}}"/>
</Style>
Converter:
public class BackgroundConverter:IValueConverter
{
public BackgroundConverter()
{
this.Brushes = new Dictionary<string, Brush>();
}
public Dictionary<string, Brush> Brushes {get;set;}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is string)
{
string key = value.ToString();
if (this.Brushes.ContainsKey(key))
return this.Brushes[value.ToString()];
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The following will set the colors to use for fields with Key1 and Key2:
BackgroundConverter backgroundConverter = this.Resources["BackgroundConverter"] as BackgroundConverter;
backgroundConverter.Brushes.Add("Key1", Brushes.Green);
backgroundConverter.Brushes.Add("Key2", Brushes.Yellow);
If you are reusing the same FieldLayout for multiple fields, then you could use the InitializeRecord event and change the style to bind to the Tag of the DataRecord like this:
XAML:
<Style TargetType="{x:Type igDP:DataRecordPresenter}">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=Record.Tag}"/>
</Style>
C#:
void XamDataGrid1_InitializeRecord(object sender, Infragistics.Windows.DataPresenter.Events.InitializeRecordEventArgs e)
{
if (!e.ReInitialize)
{
// Set the tag to the desired brush.
e.Record.Tag = Brushes.Blue;
}
}
Note that I didn't add the conditional logic for determining the brush to use and that still needs to be done for different levels to have different backgrounds.

Nesting a Progress Bar into a Combobox in WPF

Is it possible to nest a Progress bar into a combobox or the other way around. I want to be able to type into the combo box and hit a button and the progress bar shows the progress of the event, like in Windows Explorer.
EDIT: I need the code in Visual Basic.NET 3.5 Thanks.
Here's one way to do it, basically what I've done is:
Subclass ComboBox and add IsProgressVisible and ProgressValue dependency properties
Add a green rectangle the the ComboBox control template exactly behind the editable area
Bind the rectangle visibility to IsProgressVisible and the rectangle width (using a ScaleTransform) to ProgressValue
First the new control code:
public class ProgressCombo : ComboBox
{
public static readonly DependencyProperty IsProgressVisibleProperty =
DependencyProperty.Register("IsProgressVisible", typeof(bool), typeof(ProgressCombo));
public bool IsProgressVisible
{
get { return (bool)GetValue(IsProgressVisibleProperty); }
set { SetValue(IsProgressVisibleProperty, value); }
}
public static readonly DependencyProperty ProgressValueProperty =
DependencyProperty.Register("ProgressValue", typeof(double), typeof(ProgressCombo));
public double ProgressValue
{
get { return (double)GetValue(ProgressValueProperty); }
set { SetValue(ProgressValueProperty, value); }
}
}
There's also a value converter we'll use:
public class FromPercentConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((double)value) / 100;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Now take the combo box sample style from MSDN (.net 3.5 version, not 4) from http://msdn.microsoft.com/en-us/library/ms750638%28VS.90%29.aspx
Add an xmlns:l definition to your own assembly
Now change <Style x:Key="{x:Type ComboBox}" TargetType="ComboBox"> to <Style x:Key="{x:Type l:ProgressCombo}" TargetType="l:ProgressCombo">
Change <ControlTemplate TargetType="l:ComboBox"> To:
<ControlTemplate TargetType="l:ProgressCombo">
<ControlTemplate.Resources>
<BooleanToVisibilityConverter x:Key="Bool2Vis"/>
<l:FromPercentConverter x:Key="FromPercent"/>
</ControlTemplate.Resources>
Locate the line <ContentPresenter and add before it:
<Rectangle
Fill="LightGreen"
Margin="3,3,23,3"
Visibility="{TemplateBinding IsProgressVisible, Converter={StaticResource Bool2Vis}}">
<Rectangle.RenderTransform>
<ScaleTransform ScaleX="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ProgressValue, Converter={StaticResource FromPercent}}"/>
</Rectangle.RenderTransform>
</Rectangle>
And that's it
I had a similar sort of requirement for a different reason (i had a combo that was auto-populating after a network scan). See if this question & answer helps you: WPF ComboBox - showing something different when no items are bound

Binding for WPF Styles

I'm trying to create a custom control - a button - which will have multiple styles applied to it depending on the value of a property within the data context.
What I was thinking is using something similar to:
<Button Style="{Binding Path=ButtonStyleProperty, Converter={StaticResource styleConverter}}" Text="{Binding Path=TextProp}" />
And in code... Implement an IValueConverter which does something similar to the code below in the ConvertTo method:
switch(value as ValueEnums)
{
case ValueEnums.Enum1:
FindResource("Enum1ButtonStyle") as Style;
break;
... and so on.
}
However I'm not entirely sure about how to pull out the style object and even if this is possible at all...
What I am doing in the mean time is handling the DataContextChanged event, then attaching a handler to the PropertyChanged event of the object being bound to the button - then running the switch statement in there.
Its not quite perfect but until I can find a better solution it seems like that is what I'll have to use.
If you want to replace the whole style (rather than just elements of it) then you'll probably be storing those styles in resources. You should be able to do something along the lines of:
<Button>
<Button.Style>
<MultiBinding Converter="{StaticResource StyleConverter}">
<MultiBinding.Bindings>
<Binding RelativeSource="{RelativeSource Self}"/>
<Binding Path="MyStyleString"/>
</MultiBinding.Bindings>
</MultiBinding>
</Button.Style>
</Button>
By using a MultiBinding and using Self as the first binding we can then lookup resources in our converter. The converter needs to implement IMultiValueConverter (rather than IValueConverter) and can look something like this:
class StyleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
FrameworkElement targetElement = values[0] as FrameworkElement;
string styleName = values[1] as string;
if (styleName == null)
return null;
Style newStyle = (Style)targetElement.TryFindResource(styleName);
if (newStyle == null)
newStyle = (Style)targetElement.TryFindResource("MyDefaultStyleName");
return newStyle;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
It's not something I do very often, but that should work from memory :)
It seems that you need to use DataTrigger class. It allows you to apply different styles to your button based on it's content.
For example following style will change button's background property to red based on value of data context object's property
<Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path="Some property"}"
Value="some property value">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
For those of us who can't use multi value converter (I'm looking at you SL4 and WP7:), thanks to Steven's answer I found a way using an ordinary value converter.
The only assumption is the style value is contained within the property of the style being set.
So if you're using the MVVM pattern then the style value (such as TextSmall, TextMedium, TextLarge) is assumed to be part of the view model, and all you have to do is pass the converter parameter defining the name of style.
For example, say your view model has property:
public string ProjectNameStyle
{
get { return string.Format("ProjectNameStyle{0}", _displaySize.ToString()); }
}
Application style:
<Application.Resources>
<Style x:Key="ProjectNameStyleSmall" TargetType="TextBlock">
<Setter Property="FontSize" Value="40" />
</Style>
<Style x:Key="ProjectNameStyleMedium" TargetType="TextBlock">
<Setter Property="FontSize" Value="64" />
</Style>
<Style x:Key="ProjectNameStyleLarge" TargetType="TextBlock">
<Setter Property="FontSize" Value="90" />
</Style>
XAML view:
<TextBlock
Text="{Binding Name}"
Style="{Binding ., Mode=OneWay, Converter={cv:StyleConverter}, ConverterParameter=ProjectNameStyle}">
With your StyleConverter class implementing IValueConverter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType != typeof(Style))
{
throw new InvalidOperationException("The target must be a Style");
}
var styleProperty = parameter as string;
if (value == null || styleProperty == null)
{
return null;
}
string styleValue = value.GetType()
.GetProperty(styleProperty)
.GetValue(value, null)
.ToString();
if (styleValue == null)
{
return null;
}
Style newStyle = (Style)Application.Current.TryFindResource(styleValue);
return newStyle;
}
Note that this is WPF code, as the converter is derived from a MarkupExtension as well as IValueConverter, but it will work in SL4 and WP7 if you use static resource and add a bit more leg work as the TryFindResource method doesn't exist.
Hope that helps someone, and thanks again Steven!
ViewModel
private Style _dynamicStyle = (Style)Application.Current.FindResource("Style1");
public Style DynamicStyle
{
get { return _dynamicStyle; }
set
{
_dynamicStyle = value;
OnPropertyChanged("DynamicStyle");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Implement a property in your ViewModel and then dynamically change style where ever you want like below.
DynamicStyle=(Style)Application.Current.FindResource("Style2");// you can place this code where the action get fired
View
Then set DataContext value and then implement the following code in your view
<Button Style="{Binding DynamicStyle,Mode=TwoWay}"/>

Resources