WPF Image Source binding with StringFormat - wpf

I'm new to WPF and MVVM (started this week experimenting with it) and trying to bind image resources at runtime. The items I'm trying to display contain an enumerate property that indicates the type or state of the item:
public class TraceEvent
{
/// <summary>
/// Gets or sets the type of the event.
/// </summary>
/// <value>The type of the event.</value>
public TraceEventType EventType { get; set; }
}
As far as I known the Source attribute of Image has a value converter that takes strings and returns Uri objects.
<Image Source="{Binding Path=EventType, StringFormat={}/AssemblyName;component/Images/{0}Icon.ico}" />
So why doesn't the above work? If I enter the uri directly (without binding) the image is shown perfectly. In fact, if I do the binding in a TextBlock and use the result of that value in the Image also shown without problem:
<TextBlock Visibility="Collapsed" Name="bindingFix" Text="{Binding Path=EventType, StringFormat={}/AssemblyName;component/Images/{0}Icon.ico}"/>
<Image Source="{Binding ElementName=bindingFix, Path=Text}" />
I'm pretty sure I'm doing something terrible wrong for such an obvious thing to do with images.
Thanks.

StringFormat is only used if the target property is actually a string - the Image.Source property is a Uri so the binding engine won't apply the StringFormat.
One alternative is to use a Value Converter. Either write a general StringFormatConverter that takes the string format in the ConverterParameter, or a more specific ImageSourceConverter e.g.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return string.Format( "/Images/{0}Icon.ico", value );
}
Note that if your images live in the same assembly as they are used, then you shouldn't need to specify the assembly name in the URI and the above syntax should work.

i'm not sure about this one but it seems that you are passing the image's source property a string where it expects a uri. so, you have to convert your string into a uri object

Related

Binding OneWayToSource with OneTime initalization of target

I have a DataGrid with editable cells bound to their respective values in the view model of the respective items.
Initially, the data is loaded and displayed to the user, who can then edit the data in the grid.
Binding is working as it should (in my case with UpdateSourceTrigger=OnPropertyChanged), but due to conversions between double (view model) and string (UI), a TwoWay binding would cause annoying UI bugs like making decimal separators or zeros after a decimal point disappear when typed by the user.
Two faulty solutions are:
Making the property a string in the view model, and doing the necessary conversions inside the view model.
Problem: brings me a strange problem of incompatible cultures between the UI and the view model (and I don't expect the view model to know the UI's culture)
Using a OneWayToSource binding. This eliminates all UI bugs as the VM stops sending back parsed and reconverted values.
Problem: I can't (or don't know how to) initialize the values in the grid with the loaded data.
So, can I somehow use a OneWayToSource binding "after" a OneTime binding, or somehow sum the two?
I tried to bind FallbackValue and TargetNullValue to the source values, but they don't accept bindings.
The decimal place disappearing is a "feature" they introduced whilst trying to fix something else. I thought it was .Net 4.0 this was introduced and people started noticing it was a breaking change but the documentation seems to imply .Net 4.5.
It usually occurs because you set updatesourcetrigger=propertychanged.
The simple fix is often to just remove that.
Because
, UpdateSourceTrigger=LostFocus
Is the default behaviour for textbox text binding.
Alternatively, you could experiment with KeepTextBoxDisplaySynchronizedWithTextProperty
https://msdn.microsoft.com/en-us/library/system.windows.frameworkcompatibilitypreferences.keeptextboxdisplaysynchronizedwithtextproperty(v=vs.110).aspx
public MainWindow()
{
FrameworkCompatibilityPreferences.KeepTextBoxDisplaySynchronizedWithTextProperty = false;
InitializeComponent();
}
You can set that in Mainwindow before anything is displayed.
I found out a hacky workaround that involves using two properties, the original and a string dedicated to the user for smooth behavior. Use a specific converter for that purpose. (I think I'll adopt this as a pattern for future cases)
This works for cases where the view model does not change the property, only the user changes the property. (If you want a truly two way interaction where the view model also changes the property, you need to set the string property to null whenever you need to change the property)
In the view model:
The differences from a standard code are:
adding a string property without logic
adding a notification for this property when the original property changes
Code:
private double? _TheProperty;
public double? TheProperty { get { return _TheProperty ; } set { SetTheProperty (value); } }
public string ThePropertyUserString { get; set; } //for UI only!!! Don't change via code
private void SetTheProperty(double? value)
{
if (value == null)
{
//implement validation errors if necessary
//using IDataErrorInfo and ValidatesOnDataErrors
//this type of validation is the only I found that helps enabling/disabling command buttons
}
//do your logic
_TheProperty = value;
//notify
if (PropertyChanged != null)
{
PropertyChanged("TheProperty", ...);
PropertyChanged("ThePropertyUserString", ...);
}
}
In the XAML:
<TextBox Style="{StaticResource ErrorStyle}">
<TextBox.Text>
<MultiBinding UpdateSourceTrigger="PropertyChanged"
Mode="TwoWay"
Converter="{StaticResource DoubleUserStringConverter}">
<Binding Path="TheProperty" ValidatesOnDataErrors="True"/>
<Binding Path="ThePropertyUserString"/>
/MultiBinding>
</TextBox.Text>
</TextBox>
The converter:
/// <summary>
/// multibinding, first binding is double? and second is string, both representing the same value
/// the double? value is for the viewmodel to use as normally intended
/// the string value is for the user not to have ui bugs
/// </summary>
class DoubleUserStringConverter : IMultiValueConverter
{
private OriginalConverterYouWanted converter;
public DoubleUserStringConverter()
{
converter = new OriginalConverterYouWanted(); //single binding, not multi
//for types "double" in the view model and "string" in the UI
//in case of invalid strings, the double value sent to UI is null
}
//from view model to UI:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[1] == null) //null string means UI initialization, use double
return converter.Convert(values[0], targetType, parameter, culture);
else
return values[1]; //in the rest of the time, send user string to UI
}
//from UI to view model
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
return new object[] {
converter.ConvertBack(value, targetTypes[0], parameter, culture), //can be null
value //string is always sent as is, no changes to what the user types
};
}
}

UWP subclassed ComboBox control in C# for enum - Display

I've developed a subclassed ComboBox control in C#, UWP, for enum type properties.
It works great! Almost all the time (... types).
Problem surfaced when the type of the enum was Windows.UI.Text.FontStyle.
The item selection still works right, but what it displays is not the .ToString() values, but Windows.Foundation.IReference`1<Windows.UI.Text.FontStyle> for each item.
When I debug, everything is the same and fine as far as my code is concerned.
My control works by a DependencyProperty called SelectedItemEnum - SelectedItemEnumProperty, which' type is object.
And by this binded concrete enum value it sets the ItemsSource:
ItemsSource = Enum.GetValues(SelectedItemEnum.GetType()).Cast<Enum>().ToList();
(And I handle the SelectionChanged event (inside the control) to set the value, but that part always works well.)
I have now been trying this for an hour or so, but I am unable to figure out why this happens and I would definitely be interested to see the real reason behind this.. Apparently there is something about the FontStyle enum, that causes it to get represented as nullable (IReference seems to be "equivalent" to nullable in .NET world).
To solve your problem however, you can build a custom ValueConverter, that will convert the value to string before displaying.
First create a ToStringConverter:
public class ToStringConverter : IValueConverter
{
public object Convert( object value, Type targetType, object parameter, string language )
{
var stringValue = value.ToString();
return stringValue;
}
public object ConvertBack( object value, Type targetType, object parameter, string language )
{
throw new NotImplementedException();
}
}
Now add is as a resource to your page or to the app itself:
<Page.Resources>
<local:ToStringConverter x:Key="ToStringConverter" />
</Page.Resources>
You can then use it with the combo box as follows:
<local:EnumComboBox x:Name="EnumComboBox">
<local:EnumComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource ToStringConverter}}" />
</DataTemplate>
</local:EnumComboBox.ItemTemplate>
</local:EnumComboBox>
This will correctly display the value of the enum. You can see it and try it out in here on my GitHub, along with the sample app I have used to try to figure this out.
I will keep trying to find the reason however, as it does really interest me :-) .

Is it possible to concatenate an imagesourece Uri in xaml

Hi I've been looking at ways to dynamically change an image Uri in xaml and in my research came across the following answer, which has certainly given me hope that what I really want to do might just be possible. In the original question the questioner wanted to swap the image itself, in my case I want to swap the directory where the image is located.
So when one looks at the answer that #Clemens provided one ends up with an images source being bound to a dependency property that is dynamically set when the form loads.
What I'd like to know is whether it would be feasible to set the location part of the uri dynamically (as per the logic that #Clemens is advocating and then simply append the Image name in the actual binding statement so that it might look something like this:
<Image Source="{Binding ImageUri & myImage.png}"/>
Essentially I have a number of buttons to which I would like to assign a default image og a size to be determined by the end user. To that end the Images would be stored in different folders in the application (in fact its a custom control) and then the relevant path bit of the URI would be set as per the suggestion in the referenced answer and I'd just append the Image name (which would be the same for the button irrespective of the size) and it would then have the correct image to display.
MainWindow.xaml.cs :
namespace MainWindowNamespace
{
public sealed class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
string fullPath = Path.GetFullPath((string)value);
return new BitmapImage(new Uri(fullPath));
}
catch { return null; }
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{ throw new NotImplementedException(); }
}
}
MainWindow.xaml :
<Window
xmlns:imgConvert="clr-namespace:MainWindowNamespace">
<Window.Resources>
<imgConvert:ImageConverter x:Key="ImageConverter" />
</Window.Resources>
<Image ImageSource="{Binding ImagePath, Converter={StaticResource ImageConverter}}" />
</Window>

Writing conditional statements in XAML code

I have this listBox that gets populated, each item can be either male or female depending on the 'SEX' property that is binded to the listBox. (Could be either 'M' for male and 'F' for female)...
For each item i would like to display either a male or female symbol based on the list items SEX property.
for instance if "{Binding SEX}" equals 'M':
<Image Source="../Images/male48.png" Visibility="Visible" />
and if "{Binding SEX}" equals 'F':
<Image Source="../Images/female48.png" Visibility="Visible" />
How exactly would I go about getting this to work?
A common approach to this problem is to create a value converter, this converts the value returned by a binding into some other value that relates to the property of a UI control.
You can create a converter that takes the sex and maps it to an image source:
public class SexToSourceConverter : IValueConverter
{
public object Convert(object value, string typeName, object parameter, string language)
{
string url = ((string)value == "M") ? "../Images/male48.png" : "../Images/female48.png";
return new BitmapImage(new Uri(url , UriKind.Relative));
}
public object ConvertBack(object value, string typeName, object parameter, string language)
{
throw new NotImplementedException();
}
}
Using it in your XAML as follows:
<Image Source="{Binding Path=Sex, Converter={StaticResource SexToSourceConverter }" />
If someone is interested in how this could work, I've made a solution based on ColinE's answer. First, you've to create a new class which contain the conditions you'll like to add to the XAML code:
public class MyNiceConverterName : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
// Your conditions here!
return value_you_want_to_return; // E.g., a string, an integer and so on
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException(); // Leave this like here, don't worry
}
}
Call the class whatever you want (right now it's called MyNiceConverterName) and implement the Convert() method with the conditions you'd like to add to the XAML file. Remember to cast the object value to the type you're using (e.g., (int)value if it's an integer).
This is almost done! But not yet, first declare the converter in your XAML as a resource. You can paste this code below the namespaces declaration:
<Control.Resources>
<converter:MyNiceConverterName xmlns:converter="clr-namespace:My_Namespace" x:Key="MyNiceConverterName" />
</Control.Resources>
You've to declare the namespace where you defined the class (i.e., remove My_Namespace with yours') and also rename MyNiceConverterName to your class name. The key will be the name defined to reference the converter within the XAML document, here I've used the same class name but you can freely change it.
Finally, it's time to use the converter. Put this and you're done:
{Binding variable_with_value, Converter={StaticResource MyNiceConverterName}}
Remember to change variable_with_value with the one you'd like to use within your binding.
I hope it helps!
Either use a binding converter or use two triggers.
For Siverlight this is the correct IValueConverter link, I am not sure if triggers are supported.

Using file-path to images stored within the Application Settings

I am trying to develop an application that uses a number of images that are stored in a seperate remote file location. The file-paths to the UI elements are stored within the Application Settings. Although I understand how to access and use the file-path from Settings in C# (Properties.Settings.Default.pathToGridImages + "OK.png"), I am at a loss to figure out how to utilize the Settings paths in WPF, and can only seem to access the file if I include the file-path, such as:
<Grid.Background>
<ImageBrush ImageSource="C:\Skins\bottomfill.png" TileMode="Tile" />
</Grid.Background>
I would have thought that concatenating "Properties.Settings.Default.pathToGridImages" with "bottomfill.png" in WPF could be done much like it can be done in C#. Can anyone please point me in the right direction?
You can do this using a MultiBinding and a value converter. To start with, use a multibinding to bind your image source to the base path, and the image name:
<ImageBrush>
<ImageBrush.ImageSource>
<MultiBinding Converter="{StaticResource MyConverter}">
<Binding Source="{StaticResource MySettings}" Path="Default.FilePath" />
<Binding Source="ImageName.png"></Binding>
</MultiBinding>
</ImageBrush.ImageSource>
</ImageBrush>
You then need to have a converter that implements IMultiValueConverter and combines the two parts of the path and creates the image using either an ImageSourceConverter or by creating a new BitmapImage:
class MyConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// Concatenate the values.
string filename = Path.Combine(values[0].ToString(), values[1].ToString());
// You can either use an ImageSourceConverter
// to create your image source from the path.
ImageSourceConverter imageConverter = new ImageSourceConverter();
return imageConverter.ConvertFromString(filename);
// ...or you can create a new bitmap with the combined path.
return new BitmapImage(new Uri(filename, UriKind.RelativeOrAbsolute));
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
// No need to implement the convert back as this will never be used in two way binding.
throw new NotImplementedException();
}
}
Obviously, you need to declare namespaces and resource to the CLR stuff in the XAML so you can access it (If you've called your settings and converter classes something different, make sure you change this to match up):
...
xmlns:local ="clr-namespace:WpfApplication1">
<Window.Resources>
<local:MyConverter x:Key="MyConverter"></local:MyConverter>
<local:MySettings x:Key="MySettings"></local:MySettings>
</Window.Resources>
I've tested it out and it works fine.
[An alternative way would be just to bind the ImageSource property to a property on your data context that combined the paths in C# code, but that would depend on how you've got your datacontexts set up, so may be undesirable in many cases.]

Resources