UserControl update image based on datatype - wpf

I have a program that displays either customer, company or employee info. I would like to display an icon next to this image with the icon changing based on the type of info I am displaying (customer, company or employee).
I have the following setup in my resource dictionary to specify the images:
<ImageSource x:Key="CompanyIcon">../Images/companies_32.png</ImageSource>
<ImageSource x:Key="EmployeeIcon">../Images/employee_32.png</ImageSource>
<ImageSource x:Key="CustomerIcon">../Images/customer_32.png</ImageSource>
In my viewmodel I would like to assign the image based on what data type I am working with. For instance if I am viewing a company's info (a DBContext of type 'Company' using EF 4.5) I want to set the image to that of 'CompanyIcon'.
How would I assign the image with the viewmodel (and change it as I change between a 'Company', 'Employee' or 'Customer' DBContext type) and then bind this image to a placeholder in the view (it will be displayed within a grid column).

I would use a DataTrigger that sets the Image.Source based on the object type, and use a Converter that returns the typeof(value) for getting the type
<Style x:Key="MyStyle" TargetType="{x:Type Image}">
<!-- Default Value -->
<Setter Property="Source" Value="{StaticResource CompanyIcon}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource ObjectToTypeConverter}}"
Value="{x:Type local:Employee}">
<Setter Property="Source" Value="{StaticResource EmployeeIcon}" />
</DataTrigger>
<DataTrigger Binding="{Binding Converter={StaticResource ObjectToTypeConverter}}"
Value="{x:Type local:Customer}">
<Setter Property="Source" Value="{StaticResource CustomerIcon}" />
</DataTrigger>
</Style.Triggers>
</Style>
The converter I usually use just looks like this:
public class ObjectToTypeConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return null;
return value.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}

What I did was to have a string property in the VM pointing to the image location (don't know if it's the best approach, but it worked quite well for me):
private string _imageSource;
public string ImageSource
{
get
{
return _imageSource;
}
set
{
_imageSource = value;
NotifyPropertyChanged(() => ImageSource);
}
}
public void SetImage()
{
If (customer)
ImageSource = "../Images/companies_32.png";
...
}
in XAML:
<Image Source="{Binding ImageSource}" .../>

Related

Cancel binding depending on object type

In my WPF application I have a label which is bound to the length of a Text-property:
<Label Content="{Binding Editor.Text.Length}"/>
The Editor-object may be either a textbox or a checkbox. The textbox does have a Text-property whereas the checkbox does not.
When the Label is bound to a "checkbox-editor" it produces a warning in Visual Studio:
BindingExpression path error: 'Text' property not found on 'object'...
This is expected and I would like to know if there is any way to tell the binding engine not to try to bind this value unless the Editor-object is a textbox?
Is the Editor property of your viewmodel a control? I hope not, but anyway.
You could write a valueconverter that returns the type of the value, and then set the Label's Content via a series of triggers in a Style. If type of Editor is {x:Type TextBox}, set it to the binding you've got above. If it's {x:Type CheckBox}, make it `{Binding Editor.IsChecked}'.
XAML
<Label>
<Label.Style>
<Style TargetType="Label" BasedOn="{StaticResource {x:Type Label}}">
<Style.Triggers>
<DataTrigger
Binding="{Binding ElementName=Editor, Converter={local:GetTypeConverter}}"
Value="{x:Type TextBox}"
>
<Setter
Property="Content"
Value="{Binding Text.Length, ElementName=Editor}"
/>
</DataTrigger>
<DataTrigger
Binding="{Binding ElementName=Editor, Converter={local:GetTypeConverter}}"
Value="{x:Type CheckBox}"
>
<Setter
Property="Content"
Value="{Binding IsChecked, ElementName=Editor}"
/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
C#
public class GetTypeConverter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value?.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Alternatively, your viewmodel could have a readonly property which returns whatever should be in that label, since the viewmodel has Editor and knows what it is. Call it EditorLabelValue for the time being. Presumably Editor is bound to a string property or a bool property, depending on which editor it is. So both of those setters would raise PropertyChanged for "EditorLabelValue", which would return the appropriate value.
I tried to do this in pure XAML by making Editor the Content of a ContentControl and then playing with DataTemplates, but I couldn't find a way to make that work without getting an exception for reparenting Editor.

WPF - Use default value from style if property in view model is null

I have own custom control with dependency property called Maximum (double) and I have defined own style for this custom control. In custom control style is setter for Maximum (50). In application I have view model with property Maximum (double?).
<Style x:Key="MyCustomControlDefaultStyle" TargetType="controls:MyControl">
<Setter Property="Maximum" Value="50" />
</Style>
<controls:MyControl Maximum="{Binding Maximum}"
Style="{StaticResource MyCustomControlDefaultStyle}"
/>
If Maximum value in view model will be null I want to wpf automatically use default value defined in MyCustomControlDefaultStyle. Is it possible?
Thanks for advice
Can't check right now, but you could put your DefaultValue inside a static Property and then reference this value from the style and from the Fallbackvalue/TargetNullValue inside the concrete Binding like:
<Setter Property="Maximum" Value="{x:Static ns:MyStaticClass.MyStaticProp}" />
And inside the Binding like:
<controls:MyControl Maximum="{Binding Maximum, FallBackValue={x:Static ns:MyStaticClass.MyStaticProp}"
Style="{StaticResource MyCustomControlDefaultStyle}" />
Not sure if that works for FallBackValue. I will check a little later... :)
You can use a DataTrigger in your style along with a Converter:
<Style.Triggers>
<DataTrigger Binding="{Binding Maximum, Converter={StaticResource NullToBooleanConverter}}" Value="False">
<Setter Property="Text" Value="{Binding Maximum}" />
</DataTrigger>
</Style.Triggers>
The converter may look like this:
class NullToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value == null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Edit: Can't manage the syntax highlight to work...

Select control style by binding value

I have an editor view that can be used for multiple edited objects. The view model for multiple objects provides a property like Field1Multiple of type bool for each field that needs to be handled. In this case, it's only ComboBox controls for now. Whenever multiple differing values shall be indicated for that field, a certain style should be applied to that control which is defined in App.xaml. That style changes the background of the control to visualise that there is no single value that can be displayed here.
I've tried with this XAML code:
<ComboBox
ItemsSource="{Binding Project.Field1Values}" DisplayMemberPath="DisplayName"
SelectedItem="{Binding Field1}">
<ComboBox.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Field1Multiple}" Value="true">
<Setter
Property="ComboBox.Style"
Value="{StaticResource MultiValueCombo}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
But it doesn't work because I cannot set the Style property from inside a Style. If I use triggers directly on the control, there may only be EventTriggers, no DataTriggers, the compiler says.
How can I set the control's style based on a binding value? Or, how can I set a certain style for a control if a binding value is true?
(EDIT to full solution)
You can use converter:
public class AnyIsMultipleToStyle : IValueConverter
{
public Style NormalStyle { get; set; }
public Style MultiStyle { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
IList<SampleClass> list= value as IList<SampleClass>;
if (list!=null)
{
if (list.Any(i => i.Multi))
return MultiStyle;
}
}
return NormalStyle;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And in your xaml:(You indicate normal style and multistyle to converter)
<Window.Resources>
<Style x:Key="MultiValueCombo" TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="Olive" />
</Style>
<Style x:Key="NormalCombo" TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="Red" />
</Style>
<my:AnyIsMultipleToStyle x:Key="AnyIsMultipleToStyle1" MultiStyle="{StaticResource MultiValueCombo}" NormalStyle="{StaticResource NormalCombo }" />
</Window.Resources>
<Grid>
<ComboBox ItemsSource="{Binding Items, ElementName=root}" >
<ComboBox.Style>
<Binding Converter="{StaticResource AnyIsMultipleToStyle1}" Path="Items" ElementName="root" >
</Binding>
</ComboBox.Style>
</ComboBox>
</Grid>

Return a dynamic resource from a converter

I want to change the color of a WPF control depending on the state of a bool, in this case the state of a checkbox.
This works fine as long as I'm working with StaticResources:
My control
<TextBox Name="WarnStatusBox" TextWrapping="Wrap" Style="{DynamicResource StatusTextBox}" Width="72" Height="50" Background="{Binding ElementName=WarnStatusSource, Path=IsChecked, Converter={StaticResource BoolToWarningConverter}, ConverterParameter={RelativeSource self}}">Status</TextBox>
My converter:
[ValueConversion(typeof(bool), typeof(Brush))]
public class BoolToWarningConverter : IValueConverter
{
public FrameworkElement FrameElem = new FrameworkElement();
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
bool state = (bool)value;
try
{
if (state == true)
return (FrameElem.TryFindResource("WarningColor") as Brush);
else
return (Brushes.Transparent);
}
catch (ResourceReferenceKeyNotFoundException)
{
return new SolidColorBrush(Colors.LightGray);
}
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return null;
}
}
The problem is that I have several definitions of the Resource "WarningColor" dependant on setting day mode or night mode. These events does not trig the WarningColor to change.
Is there a way to make the return value dynamic or do I need to rethink my design?
You cannot return something dynamic from a converter, but if your only condition is a bool you can easily replace the whole converter with a Style using Triggers:
e.g.
<Style TargetType="TextBox">
<Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=WarnStatusSource}" Value="True">
<Setter Property="Background" Value="{DynamicResource WarningColor}" />
</DataTrigger>
</Style.Triggers>
</Style>
If now the resource with that key is changed the background should change as well.
The way to return a dynamic resource reference is pretty simple using a DynamicResourceExtension constructor and supplying it a resource key.
Usage:
return new DynamicResourceExtension(Provider.ForegroundBrush);
Definition of the Provider class should contains the key:
public static ResourceKey ForegroundBrush
{
get
{
return new ComponentResourceKey(typeof(Provider), "ForegroundBrush");
}
}
And the value for the key would be declared in the resource dictionary:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:theme="clr-namespace:Settings.Appearance;assembly=AppearanceSettingsProvider">
<Color x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type theme:Provider}, ResourceId=ForegroundColor}">#FF0000FF</Color>
<SolidColorBrush x:Key="{ComponentResourceKey {x:Type theme:Provider}, ForegroundBrush}" Color="{DynamicResource {ComponentResourceKey {x:Type theme:Provider}, ForegroundColor}}" />
</ResourceDictionary>
This way, the converter would dynamically assign a DynamicResource to the bound property depending on the resource key supplied.

WPF Data Binding and Formatting

I have a bool property in my ViewModel called IsConnected and I would like to bind it to a TextBlock in my MainWindow. Rather than have the textblock read true or false I need it to say Connected or Disconnected instead. Forgive me because I'm new to WPF. If someone could give me a head start I can take it from there but I'm not sure how to figure out what I need.
Easiest way is probably to create a custom converter which converts your bool value to a string. Search anywhere for IValueConverter and/or WPF.
public class BoolToConnectedConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if((bool)value)
return "Connected";
else
return "Disconnected";
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
add xmlns:
xmlns:converter="clr-namespace:MyProjectNameSpace"
add resource to XAML (change to whatever element needed)
<Window.Resources>
<converter:BoolToConnectedConverter x:Key="connectedConverter" />
</Window.Resources>
in XAML:
<TextBlock Text={Binding IsConnected, Converter={StaticResource connectedConverter}" />
I'd generally prefer to just add a property to the view model (I really dislike value converters), but here's a simple way to accomplish what you're trying to do using a style:
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="Connected"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="False">
<Setter Property="Text" Value="Disconnected"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Edit
Note that once you get used to using data triggers, you can make all kinds of modifications to your view without touching your view model. For instance:
<StackPanel>
<Image Source="images\connected.png">
<Image.Style>
<Style TargetType="Image">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Image Source="images\disconnected.png">
<Image.Style>
<Style TargetType="Image">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="False">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</StackPanel>
Using ViewModel, you write two property wrap, and notify changes in the real property.
So that whenever the value is changed, you the string representation will update and bind to controls, while you can still use the bool property in the code.
public string IsConnectedStr{
get{
return IsConnected?"Connected":"Disconnected";
}
}
public bool IsConnected{
get{
return _isConnected;
}
set{
_isConnected=value;
PropertyChanged("IsConnected");
PropertyChanged("IsConnectedStr");
}
}
You could do this in two ways
1) Write a converter
2) Change the function in the ViewModel so that it returns the desired string instead of a bool
The easiest way is #2, but if you really need the bool value somewhere else in your code you go with #1 (google converter and wpf)
Take a look at value converters.
http://www.wpftutorial.net/ValueConverters.html
public class BoolToConnectedConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
var isConnected = (bool)value;
return isConnected ? "Connected" : "Disconnected";
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException("Not required for read-only values");
}
}
In your XAML:
<Window.Resources>
<l:BoolToConnectedConverter x:Key="boolToConnectedConverter" />
</Window.Resources>
<Grid>
<Label Content="{Binding IsConnected, Converter={StaticResource boolToConnectedConverter}}" />
</Grid>

Resources