multibind image source in xaml & wpf - wpf

In my project, i have a folder called Images, where all the images iam using in my application are saved in subfolders.All the images are set to "Resource" in the buildprocess.
myproject
|__Images
|__AppImages
|__StarOn.png
|__StarOff.png
Now, if i do set my image manually like this:
<Image Source="Images\AppImages\StarOn.png" width="32" height="32"/>
the image is correctly shown in the imagebox.
i would like to set the image using a converter and a binding like this:
<Image>
<Image.Source>
<Binding Path="Number" converter="{StaticResource GetImagePathConverter}"/>
</Image.Source>
</Image>
where the number is an integer
and my converter is:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int questionNr=int.parse(value.ToString());
if (questionNr>100)
{
return "Images\\AppImages\\StarOn.png";
}
return "Images\\AppImages\\starOff.png";
}
but this is not changing the image ?..
what iam doing wrong ?
how can i set the image source correctly from the converter ?
thanks in advance

Your way of using converter is incorrect. You need to create an instance of your converter use it in your binding through StaticResource. local: is the local namespace which you need to declare in your xaml -
<Image>
<Image.Resources>
<local:GetImagePathConverter x:Key="GetImagePathConverter"/>
</Image.Resources>
<Image.Source>
<Binding Path="Number" Converter="{StaticResource GetImagePathConverter}"/>
</Image.Source>
</Image>
Also, Source property is not of type string but instead ImageSource so you need something in your converter -
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int questionNr=int.parse(value.ToString());
if (questionNr>100)
{
return new BitmapImage(new Uri("Images\\AppImages\\StarOn.png", UriKind.Relative));
}
return new BitmapImage(new Uri("Images\\AppImages\\StarOff.png", UriKind.Relative));
}

See this answer.
Basically, you have to take care of the type of object that you return in your converter, you cannot return string to a property of ImageSource type.
I'm not on my dev machine, but the code is something like this:
return new BitmapImage(new Uri(the/path/to/image.png)).Source; //or '*.ImageSource', can't remember

Related

Why is my ImageSource Binding not updated?

I'm trying to dynamically bind an Image Source in XAML to an URI in ViewModel (MVVM). This works fine for the initial URI, the picture "C:\tmp\Test.png" is shown. But if I set another URI to ImageURI in ViewModel the picture is not updated. Can anyone help me?
XAML:
<Image x:Name="UserImage" Stretch="Fill" Grid.Row="0">
<Image.Source>
<BitmapImage CreateOptions="IgnoreImageCache" UriSource="{Binding ImageURI, UpdateSourceTrigger=Explicit, Mode=TwoWay}"/>
</Image.Source>
</Image>
ViewModel:
public string imageURI = "C:\\tmp\\Test.png";
public string ImageURI
{
get
{
return imageURI;
}
set
{
imageURI = value;
this.OnPropertyChanged("ImageURI");
}
}
BitmapImage implements ISupportInitialize. This means that property changes are ignored after initialization. Changing the Binding's source property has no effect.
You should directly bind the Image's Source property. Built-in automatic type conversion will create a BitmapFrame behind the scenes.
<Image Source="{Binding ImageURI}" .../>
Setting UpdateSourceTrigger=Explicit and Mode=TwoWay on the Binding is pointless.
If you need to explicitly create a BitmapImage (e.g. one where the IgnoreImageCache option is set), write an appropriate Binding Converter.
Pretty old question here with no solution. Sooo since i had a similar problem and used a slightly different approach, here is my solution:
Use a converter which returns a BitmapImage in this way:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
BitmapImage error = new();
error.BeginInit();
// OnLoad will give you errors just with the start of your application and
// and won't hide somewhere in your log
error.CacheOption = BitmapCacheOption.OnLoad;
error.UriSource = new Uri(#"pack://application:,,,/Images/error.png");
error.EndInit();
error.Freeze();
return error;
}
The binding:
<Image Source="{Binding Status, Converter={StaticResource enumAnalyzerStatusConverter}, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
The OnLoad option will give an error on startup if something is bad with your image like the path. Remember to set the image in your project explorer to Ressource.

Manage the targetType of a Binding in a MultiBinding

So, I have a multi-binding with a converter that takes in some values and finds the max of them. The problem is that one of the bindings utilises a converter that expects a double target type, while the binding has an object target type. I was wondering if there was any way to modify the target type of a binding in any way.
Below is an approximation of my xaml:
<TextBlock>
<TextBlock.Width>
<MultiBinding Converter="{StaticResource _maxValueConverter}">
<Binding Source="{StaticResource _constantZeroValue}"/>
<Binding Path="ActualWidth"
ElementName="_previousTextBlock"
Converter="{StaticResource _requiresDoubleTargetConverter}"/>
</MultiBinding>
</TextBlock.Width>
</TextBlock>
So basically if there is any way to tell the second binding that it is outputting to a double value, that'd be great.
Minimal Verifiable Complete Example:
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:WpfApplication1"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<StackPanel.Resources>
<sys:Double x:Key="constantZero">0</sys:Double>
<local:RequiresDoubleTargetConverter x:Key="requiresDoubleTargetConverter" />
<local:MaxValueConverter x:Key="maxValueConverter" />
</StackPanel.Resources>
<Border x:Name="topBorder"
BorderThickness="1"
BorderBrush="Black"
HorizontalAlignment="Left">
<TextBlock x:Name="topTextBlock"
Background="Aqua"
Text="{Binding TopText}" />
</Border>
<Border BorderThickness="1"
BorderBrush="Black"
MinWidth="100"
HorizontalAlignment="Left">
<TextBlock Background="ForestGreen"
Text="{Binding BottomText}"
TextWrapping="Wrap"
MinWidth="100">
<TextBlock.Width>
<MultiBinding Converter="{StaticResource maxValueConverter}">
<MultiBinding.Bindings>
<Binding Path="ActualWidth" ElementName="topTextBlock" Converter="{StaticResource requiresDoubleTargetConverter}" />
<Binding Source="{StaticResource constantZero}" />
</MultiBinding.Bindings>
</MultiBinding>
</TextBlock.Width>
</TextBlock>
</Border>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public string TopText
{
get { return "Hello World!"; }
}
public string BottomText
{
get { return "hi earth."; }
}
public MainWindow()
{
InitializeComponent();
}
}
public class RequiresDoubleTargetConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// I am looking for a way to manually ensure that "targetType == typeof(double)" evaluates to true.
if (targetType != typeof(double))
{
return null;
}
else
{
// Actual converter performs this calculation.
return (double)value - 14;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// Irrelevant method for our purposes.
throw new NotImplementedException();
}
}
public class MaxValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double max = double.NegativeInfinity;
foreach (object value in values)
{
if (value is double)
{
max = Math.Max((double)value, max);
}
else
{
Debug.Fail("All values must be doubles");
}
}
return max;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
// Irrelevant method for our purposes.
throw new NotImplementedException();
}
}
}
This was created using Visual Studio 2015, and is verified to show the erroneous behaviour. What I am trying to determine is if it is possible to manually set the targetType of the RequiresDoubleTargetConverter from the xaml.
Bindings operate on object, as far as the type system is concerned. If you want a specific type, you'll need to ensure that yourself.
However you could use the target type passed to the converter to determine which type is required, and modify the converter's return value accordingly.
So is there a way to set the target type of the converter manually, or am I stuck with it being object?
You are stuck with it being object as the signature of the Convert method is always the same, i.e. it accepts an object[] of values and nothing else.
You will have to cast values[1] to a double in your Convert method (because the method will always and only be passed values of type object):
double d = (double)values[1];
I've come up with a way around this, but it only works if you have access to the converter you're using on the MultiBinding itself (or you can add one.) as well as a little extra effort if it also uses a ConverterParameter, TargetNullValue and/or a StringFormat.
The trick is when you add the child Binding to the MultiBinding, you remove the Converter, ConverterParameter, TargetNullValue and StringFormat values from that child binding and store them somewhere that's accessible by the Convert method for the MultiBinding's converter. (We use a wrapper MarkupExtension to simulate the MultiBinding so we have access to everything before they're actually applied as once they are, you can't change them.)
Then in the MultiBinding's Convert method, you're now getting the raw, not-yet-converted/formatted/coalesced value from the child binding, but you also have the ultimate target that you need (double in this example) as it was handed to the Convert method for the MultiBinding that you're in.
With that information, you then call the child converter's Convert method manually, passing in the not-yet-converted value, the targetType (passed in to you), and the childConverterParameter.
You take the result of that call, and if null, return the TargetNullValue from the child binding.
If it's not null and both targetType is a string and you have a String Format, finally format the results.
Here's pseudo-code (i.e. off the top of my head. Prolly lots of syntax errors, etc. For the actual code, you can see me using it in my DynamicResourceBinding class that I have up on StackOverflow here.)
// Convert function for the MultiBinding
private object Convert(object[] values, Type targetType, object parameter, Culture culture){
var rawChildBindingResult = values[0]; // assuming it's in the first position
var convertedChildBindingResult = childConverter(rawChildBindingResult, targetType, childConverterParameter, culture);
if(convertedChildBindingResult == null)
convertedChildBindingResult = childTargetNullValue;
else if(targetType == typeof(string) && childStringFormat != null)
convertedChildBindingResult = string.Format(childStringFormat, convertedChildBindingResult);
// Now do whatever you would with the 'convertedChildBindingResult' as if things were normal
}
Again, take a look at the link to see it in context.
Hope this helps!

How to implement a Converter

I have a list of objects that I'm binding to the screen. One of the properties is isPurchased. It is a Boolean type.
I don't have a lot of experience with converters so I'm finding this a little difficult. I have 2 questions.
The 1st question is regarding syntax. I copied this example from here.
public class purchasedConverter : IValueConverter
{
public object Convert(inAppPurchases value, Type targetType, object parameter, string language)
{
return;
}
}
If the isPurchased == true then I'd like to set the background color to my stackpanel to a different color.
I changed object value to inAppPurchases value on the Convert method. However, no matter what I tried I could not get a reference to a Background.
I think I want to return Background="somecolor"
My 2nd question (assuming I can do the 1st part), is I'm using StandardStyles.xaml which comes with the Microsoft WinRT projects So my converter would exist there.
<StackPanel Grid.Column="1" VerticalAlignment="Top"
Background="CornflowerBlue" Orientation="Vertical" Height="130"
Margin="0,0,5,0"/>
However, like I said I've tried this before but I wasn't able to figure out how to add the convert to my .xaml file. Where would I reference the converter? Is it on the StandardStyls.xaml or the main .xaml that I'm viewing?
Any help is appreciated.
Background property of StackPanel is type of Brush (Panel.Background msdn) , so we can return object of type SolidColorBrush from Convert method.
Your converter should look like this:
class PurchasedConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// isPurchased is bool so we can cast it to bool
if ((bool)value == true)
return new SolidColorBrush(Colors.Red);
else
return new SolidColorBrush(Colors.Orange);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Next you must create instance of this converter in XAML:
<Window.Resources>
<con:PurchasedConverter x:Key="pCon" />
</Window.Resources>
And now you can use this converter to binding Background property in StackPanel:
<StackPanel VerticalAlignment="Top" Orientation="Vertical" Height="130"
Background="{Binding isPurchased, Converter={StaticResource pCon}}"
Margin="0,0,5,0" >
</StackPanel>

WPF : Conditional templating of textblock

I have a bunch of textblocks in an itemscontrol... I need to know how can I underline the text in the textblock based on whether the text is available in a list in the data model..
Sounds very simple to me...but I have been googling since the past 8 hrs...
Can I use datatriggers and valueconverters for this purpose? If yes, then how can I execute the method which lies in the viewModel (the method which helps me to check whther a given a text exists in the data model list)...
Even if I go for conditional templating....how do I access the list which lies in my model (the viewmodel can fetch it...but then how do i access the viewmodel?)..
This should be a fairly easy thing to do...Am I really missing something very simple here?? :)
I am following the MVVM pattern for my application..
One way is to use a multivalueconverter which is a class that implements IMultiValueConverter. A multivalueconverter allows you to bind to several values which means that you can get a reference to both your viewmodel and the text of your TextBlock in your valueconverter.
Assuming that your viewmodel has a method called GetIsUnderlined that returns true or false indicating whether or not the text should be underlined your valueconverter can be implemented along these lines:
class UnderlineValueConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var viewmodel = values[0] as Window1ViewModel;
var text = values[1] as string;
return viewmodel.GetIsUnderlined(text) ? TextDecorations.Underline : null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
You can use this valueconverter in the following way for a TextBlock:
<Grid x:Name="grid1" >
<Grid.Resources>
<local:UnderlineValueConverter x:Key="underlineValueConverter" />
</Grid.Resources>
<TextBlock Text="Blahblah">
<TextBlock.TextDecorations>
<MultiBinding Converter="{StaticResource underlineValueConverter}">
<Binding /> <!-- Pass in the DataContext (the viewmodel) as the first parameter -->
<Binding Path="Text" RelativeSource="{RelativeSource Mode=Self}" /> <!-- Pass in the text of the TextBlock as the second parameter -->
</MultiBinding>
</TextBlock.TextDecorations>
</TextBlock>
</Grid>

WPF Custom Control with Image.

I am quite new to WPF/XAML and I am currently facing a problem.
I have a solution with two projects in it, the first project is a Custom Control Library with a custom Window form control inside. The second project is a WPF application using my custom window form.
All work fine except for the form Icon. In the WPF application project I set my window icon property to /ProjectTwoNameSpace;component/Resources/Images/Film.ico, and in the WPF custom control I try to show that image that way :
<Image Grid.Column="0" Margin="3" Width="27" Height="27">
<Image.Source>
<BitmapImage UriSource="{Binding Path=Icon}" />
</Image.Source>
</Image>
But it doesn't work, I get a error at runtime saying that the property UriSource or StreamSource must be set for my <Image> tag.
Anyone can help me ? I think it's jsut a WPF newbie problem.
The UriSource property of a BitmapImage cannot be set as you have shown because it is of type Uri and you are trying to set it to a string. I'd say the easiest way to accomplish what you're doing is to bind your Image.Source to Icon, and then convert the string to a bitmap Image object. Assuming your control is in a window, this would look something like this
<Window.Resources>
<converters:StringToBitmapImageConverter x:Key="stringToBitmapImageConverter" />
</Window.Resources>
...
<Image Grid.Column="0" Margin="3" Width="27" Height="27"
Source="{Binding Path=Icon, Converter={StaticResource stringToBitmapImageConverter}}">
</Image>
And then the converter would look like:
class StringToBitmapImageConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
BitmapImage image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri(value as string);
image.EndInit();
return image;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}

Resources