WPF binding to ViewModel property from the DataTemplate Style - wpf

I am trying to bind ForeGround color of all TextBlock items to a ViewModel property. The TextBlock elements locate under a Grid that itself is defined under DataTemplate. This whole code is defined under a UserControl.
I am trying to use RelativeSource binding to find the UserControl's DataContext and get the property I need.
XAML:
<my:MapControl>
<my:MapControl.Resources>
<ResourceDictionary>
<DataTemplate x:Key="SomeTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="TextElement.Foreground" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.TextColor}" />
</Style>
</Grid.Style>
<TextBlock Grid.Column="0" />
<TextBlock Grid.Column="1" />
</Grid>
</DataTemplate>
</ResourceDictionary>
</my:MapControl.Resources>
</my:MapControl>
ViewModel:
public class MapViewModel
{
public virtual string TextColor
{
get { return _textColor; }
set
{
_textColor = value;
this.RaisePropertyChanged("TextColor");
}
}
private string _textColor = "Black";
}
The above binding doesn't work. If I change the Value binding to a hard-coded value, like "Red" for example, the Foreground color on those TextBlocks are showing correctly.
How to get the binding to work with this setup?

Analysis
It seems the root cause — binding to an instance of the string type instead of an instance of the Brush type.
Some of the possible solutions:
Change the type of the TextColor property of the MapViewModel class to from the string type to the SolidColorBrush type and update the implementation of the MapViewModel class appropriately.
Create custom implementation of the IValueConverter interface which takes the string as the input and outputs an instance of the SolidColorBrush type.

What version of .NET are you using? Works fine with 4.5 but IIRC it didn't with earlier versions and you had to declare a solidcolorbrush explicitly:
<Style TargetType="Grid">
<Setter Property="TextElement.Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext.TextColor}" />
</Setter.Value>
</Setter>
</Style>
Whatever you do don't create a brush or any other UI resources in your viewmodel, it's a violation of MVVM.

Related

Accessing a static object from Style/template in Generic.xml?

I have a double called LoadAnimAngle which simply holds the angle of a spinning loading icon, which gets rotated over time. This variable is defined in my MainViewModel class. I'm using this same variable across all places that has a spinning loading icon.
I need it inside a custom control that is defined in Generic.xml with a style/template. Here is the part where I'm binding to LoadAnimAngle:
<v:ColoredImage Image="{StaticResource LoadingIcon}" Color="{StaticResource DarkBlueClick}" RenderTransformOrigin="0.5, 0.5" VerticalAlignment="Center" Width="32" Height="32" Margin="0,0,0,0" Visibility="{Binding IsBusy, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BooleanToVisibility}}">
<v:ColoredImage.RenderTransform>
<RotateTransform Angle="{Binding MainViewModel.LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}}"/> //here is the error
</v:ColoredImage.RenderTransform>
</v:ColoredImage>
The custom control has a property that is binding to my instance of MainViewModel, like so:
public MainViewModel MainViewModel { get { return MainViewModel.instance; } }
Inside the constructor of MainViewModel I simply set:
instance = this;
The problem is that Generic.xml gets loaded before my MainViewModel class, causing the instance to be null for the frame before the graphics have loaded, after everything is done loaded, everything works. How could I resolve this problem?
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=MainViewModel.LoadAnimAngle; DataItem=null; target element is 'RotateTransform' (HashCode=66939890); target property is 'Angle' (type 'Double')
Notice: I do understand that the error is harmless and does not effect anything for the end user, however seeing that error every time I debug causes me emotional pain.
I need to somehow load MainViewModel before Generic, OR, tell xaml to not try to get the data from LoadAnimAngle until MainViewModel != null.
EDIT
I get the same error after I made changes so that I do not directly bind to the instance of MainViewModel. So I think my evaluation of the case of the problem is wrong.
I added
public double LoadAnimAngle
{
get
{
if (MainViewModel.instance != null)
{
return MainViewModel.instance.LoadAnimAngle;
}
return 0;
}
}
to the view model (instead of return MainViewModel.instance)
Then I changed the binding to:
Angle="{Binding Path=LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}"
I get the same error:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=LoadAnimAngle; DataItem=null; target element is 'RotateTransform' (HashCode=21529561); target property is 'Angle' (type 'Double')
If the problem is not that the MainViewModel.instance is NULL, then what is it that causes the problem? I have problems decoding the language in the error message. What exactly is wrong and why?
EDIT 2
Relevant context (?)
<Style TargetType = "{x:Type v:ComPortButton}" >
<Setter Property = "Background" Value = "{StaticResource Milky}"/>
<Setter Property = "ColorPalette" Value = "{StaticResource MilkyPalette}"/>
<Setter Property = "Foreground" Value = "{StaticResource Black}"/>
<Setter Property = "BorderColor" Value = "{StaticResource Milky}"/>
<Setter Property="IsBasicTextButton" Value="False"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type v:ComPortButton}">
<Grid>
<Grid Visibility="{Binding Path=IsBasicTextButton, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource InverseBooleanToVisibility}}">
<Border BorderBrush="{TemplateBinding BorderColor}" Background="{TemplateBinding Background}" Width="128" Height="140" BorderThickness="1"/>
//REMOVED IREELEVANT CODE
<v:ColoredImage Image="{StaticResource LoadingIcon}" Color="{StaticResource DarkBlueClick}" RenderTransformOrigin="0.5, 0.5" VerticalAlignment="Center" Width="32" Height="32" Margin="0,0,0,0" Visibility="{Binding IsBusy, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BooleanToVisibility}}">
<v:ColoredImage.RenderTransform>
<RotateTransform Angle="{Binding MainViewModel.LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}}"/> //here is the error
</v:ColoredImage.RenderTransform>
</v:ColoredImage>
</Grid>
//REMOVED IRRELEVANT CONTROL
</Grid>
//REMOVED IRRELEVANT CONTEXT MENU
</Grid>
//REMOVED IRRELEVANT TRIGGERS
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
EDIT 3
The source of the error seems to be completely different from I first thought. The error seems to have something to do with RenderTransform, because I can access the property without errors from other places.
Like this:
// NO ERROR FOR TEXT BLOCK
<TextBlock Text="{Binding MainViewModel.LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}"/>
<v:ColoredImage Image="{StaticResource LoadingIcon}" Color="{StaticResource DarkBlueClick}" RenderTransformOrigin="0.5, 0.5" VerticalAlignment="Center" Width="32" Height="32" Margin="0,0,0,0" Visibility="{Binding IsBusy, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BooleanToVisibility}}">
<v:ColoredImage.RenderTransform>
// ERROR FOR ROTATETRANSFORM
<RotateTransform Angle="{Binding MainViewModel.LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}"/>
</v:ColoredImage.RenderTransform>
</v:ColoredImage>
But I also get the error when I do not reference MainViewModel. I created a new property like this:
public double LoadAnimAngle
{
get
{
return 0;
}
}
Then I used it in the Template like this:
<v:ColoredImage Image="{StaticResource LoadingIcon}" Color="{StaticResource DarkBlueClick}" RenderTransformOrigin="0.5, 0.5" VerticalAlignment="Center" Width="32" Height="32" Margin="0,0,0,0" Visibility="{Binding IsBusy, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BooleanToVisibility}}">
<v:ColoredImage.RenderTransform>
<RotateTransform Angle="{Binding LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}"/>
</v:ColoredImage.RenderTransform>
</v:ColoredImage>
But i get the EXACT same error!
So, the property works, everything works. It's just that RenderTransform is like outside of the VisualTree for the first frame when it is instantiated? Or something like that, i guess? Something different is happening in RenderTransform that makes it so it doesnt like my binding.
And i probably wasnt clear about the structure.
ComPortButton is a Custom Control (.cs file with Template/Style in Generic.xml).
ComPortButton uses ComPortVM as it's DataContext.
I want to access the spinning value globally, different controls, different windows, different everything, globally.
I have a MainViewModel in which i currently store the value, since it gives global access, since it
EDIT 4
Solved it and posted the solution below
After i figured it out that it was RenderTransform that was the problem and not anything else it was easy to find solutions online, seems that many people have had the same problem.
Here is the Thread that helped me solve it
The problem had something to do with VisualTree, that RenderTransform in the Template isnt hooked up to the VisualTree before the entire Control is loaded. Or something like that.
When binding like this to RotateTransform:
<v:ColoredImage.RenderTransform>
<RotateTransform Angle="{Binding LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}"/>
</v:ColoredImage.RenderTransform>
The problem occurs. But for some reason that i did not understand, you can get rif of the error by binding to RenderTransform instead. But for that you need a Converter.
[ValueConversion(typeof(double), typeof(RotateTransform))]
public class AngleToTransform : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return new RotateTransform((double)value);
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
Then use the converter like so:
<v:ColoredImage RenderTransform="{Binding MainViewModel.LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource AngleToTransform}}"/>
Your control must be independent from the concrete view model type. Instead, bind internals of the control to dependency properties on this control. Then let the external view model bind to this properties (or set them locally).
This way you remove the tight coupling between the control and the DataContext, which drastically simplifies the implementation of the control. It also allows the control to be reused with any DataContext (view model).
ComPortButton.cs
class ComPortButton : Control
{
public double Angle
{
get => (double)GetValue(AngleProperty);
set => SetValue(AnglePropertyKey, value);
}
protected static readonly DependencyProperty AnglePropertyKey = DependencyProperty.RegisterReadOnly(
"Angle",
typeof(double),
typeof(ComPortButton),
new PropertyMetadata(default));
public static readonly DependencyProperty AngleProperty = AnglePropertyKey..DependencyProperty;
public double ProgressPercentage
{
get => (double)GetValue(ProgressPercentageProperty);
set => SetValue(ProgressPercentageProperty, value);
}
public static readonly DependencyProperty ProgressPercentageProperty = DependencyProperty.Register(
"ProgressPercentage",
typeof(double),
typeof(ComPortButton),
new PropertyMetadata(default(double), OnProgressPercentageChanged));
private static void OnProgressPercentageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
double percentage = (double)e.NewValue / 100;
// Enforce an angle between 0°-360°
this.Angle = Math.Max(0, Math.Min(360, 360 * percentage));
}
}
Generic.xaml
<Style TargetType="ComPortButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type v:ComPortButton}">
<v:ColoredImage>
<v:ColoredImage.RenderTransform>
<RotateTransform Angle="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Angle}" />
</v:ColoredImage.RenderTransform>
</v:ColoredImage>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Usage example
<Window>
<Window.DataContext>
<MainViewModel />
</Window.DataContext>
<ComPortButton ProgressPercentage="{Binding ProgressPercentageValue}" />
</Window>

How to set a TargetNullValue default for all textboxes in an application?

I have an application that I would like to set all the textboxes that use a specific style to have a default binding setting of TargetNullValue='' by including it in my style definition for the textboxes.
For example I have a window w/ the following default style set for textboxes
<baseTypes:WorkspaceViewBase.Resources>
...
<Style TargetType="TextBox" BasedOn="{StaticResource TextBoxValidation}">
<Setter Property="Width" Value="100" />
</Style>
...
</baseTypes:WorkspaceViewBase.Resources>
And one of the textboxes is currently set up as
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding MyValue, TargetNullValue=''}" Visibility="{Binding MyVisibility}"/>
How can I set the 'TextBoxValidation' style up to include the TargetNullValue='' so all the texboxes would include that as the default?
You could create a custom binding type that sets a default value for the TargetNullValue property:
public class MyBinding : Binding
{
public MyBinding()
: base()
{
TargetNullValue = string.Empty;
}
public MyBinding(string path)
: base(path)
{
TargetNullValue = string.Empty;
}
}
And replace all {Binding} with this one in your XAML markup:
<TextBox Grid.Row="1" Grid.Column="1" Text="{local:MyBinding MyValue}" Visibility="{Binding MyVisibility}"/>
You cannot do this using a Style though because the {Binding} is not part of nor has anything to do with the Style.

Is Type Cast in WPF Binding possible?

I have a Catel User Control that will be acting as my tab item.
I have a tab control with the following style:
<Grid.Resources>
<Style x:Key="ShellTabItemStyle" TargetType="TabItem">
<Setter Property="HeaderTemplate">
<Setter.Value>
<!--Display the child view name on the tab header-->
<DataTemplate>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}, Mode=FindAncestor}, Path=Header}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<TabControl Grid.Row="0" Margin="10,10,10,0" ItemContainerStyle="{StaticResource ShellTabItemStyle}">
<views:OpsReport01ParameterView />
<views:NonCompliantTradesParameterView />
<views:AuditReportParameterView />
</TabControl>
My UserControl implements an interface which specifies a Header property. Is there any way to cast 'UserControl' to an interface IMyInterface? I have tried using IMyInterface as the AncestorType but this does not seem to work.
Try extending the UserControl class and adding the Header DependencyProperty into a new class:
public partial class YourUserControl : UserControl
{
// Declare Header DependencyProperty here
}
Then in your other custom UserControls, extend this YourUserControl 'base' class instead of the UserControl class:
public partial class AuditReportParameterView : YourUserControl
{
...
}
// etc.
Then you can use YourUserControl as the type in your RelativeSource Binding for all of the derived classes:
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type
YourPrefix:YourUserControl}}, Path=Header}" />

WPF: Image paths in ListBox, Display Image in place of path

I have a ListBox filled with paths of different images. How will I alter the ItemTemplate so that the images will be shown instead of paths(string).
Here is the code:
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Image Height="50" Width="50" Source="{Binding Path=Content}" Stretch="Fill"></Image>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0083A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0102A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0103A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0104A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0105A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0106A.jpg</ListBoxItem>
</ListBox>
You could make an IValueConverter that converts a string to a ImageSource.
Something like:
public class ImagePathConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new BitmapImage(new Uri(value as string));
}
public object ConvertBack(xxx) { throw new NotSupportedException(); }
}
And then create a value converter resource and use that in your binding.
a resource could be defined like:
<UserControl.Resources>
<myNameSpaceAlias:ImagePathConverter x:Key="ImagePathConverter"/>
...
and then bind with:
{Binding Path=Content, Converter={StaticResource ImagePathConverter}}
The ItemTemplate of a ListBox is copied to the ContentTemplate of a ListBoxItem during UI generation. However, when adding the ListBoxItems directly, ItemTemplate is ignored for items already of the ItemsControls container type (ListBoxItem for ListBox, ListViewItem for ListView etc.). So in this case, you'll have to use the ContentTemplate of the ItemContainerStyle directly instead.
Also, change Source="{Binding Content}" to Source="{Binding}"
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Height="50" Width="50" Source="{Binding}" Stretch="Fill"></Image>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0083A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0102A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0103A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0104A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0105A.jpg</ListBoxItem>
<ListBoxItem>C:\Users\AKSHAY\Pictures\IMG0106A.jpg</ListBoxItem>
</ListBox>
You have to use a value converter in the binding and pass a bitmap image

WPF CommandParameter not updated when set in Context Menu

I have a wpf window with several text box controls. I need to apply a common style that would apply a context menu to each control and i have defined it globally as follows,
<ContextMenu x:Key="textBoxMenu">
<Separator/>
<MenuItem Header="Affirm"
Command="{Binding Path=AffirmCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TextBox},AncestorLevel=1}}"/>
</ContextMenu>
<Style TargetType="{x:Type TextBox}" x:Key="TextBoxAffirmMenuStyle">
<Setter Property="ContextMenu" Value="{DynamicResource textBoxMenu}" />
</Style>
I Have used a Command to execute the appropriate method depending on the target of the context menu, which is in this case the text box.
To identify the controls uniquely, i have set the "Tag" property of each control with a unique string and i access this tag from the command parameter which is set to the target text box Control itself.
private bool CanAffirmExecute(object param)
{
string columnName = (param as FrameworkElement).Tag as string;
if (this.CheckIsAffirmed(columnName))
return true;
else
return false;
}
private void AffirmExecute(object param)
{
string columnName = (param as FrameworkElement).Tag as string;
this.Affirm(columnName);
}
The problem with this is that once the command parameter gets set to a particular control,
it will not change on subsequent context menu operations when right clicked on a different control. the Command parameter remains static and gets only the tag value set in the first control.
How can i get this to work so that i can access each of the tag values of the controls using the command?
thanks.
ContextMenu is at the root of its own visual tree, so any binding using RelativeSource.FindAncestor does not go past the ContextMenu.
A work around is to use a two stage binding with the PlacementTarget property as follows,
and to analyse the object parameter in the method OnAffirmCommand(object obj) to control your behaviour. In this case the object is the actual TextBox.
Here is the context menu definition:
<Window.Resources>
<ContextMenu x:Key="textBoxMenu">
<Separator/>
<MenuItem Header="Affirm"
Command="{Binding Path=AffirmCommand}"
CommandParameter="{Binding PlacementTarget.Tag,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu>
<Style TargetType="{x:Type TextBox}" x:Key="TextBoxAffirmMenuStyle">
<Setter Property="ContextMenu" Value="{StaticResource textBoxMenu}" />
</Style>
</Window.Resources>
Here are the text boxes:
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" ContextMenu="{StaticResource textBoxMenu}" Tag="{Binding RelativeSource={RelativeSource Self}}" Text="text in box 1"/>
<TextBox Grid.Row="1" ContextMenu="{StaticResource textBoxMenu}" Tag="{Binding RelativeSource={RelativeSource Self}}" Text="text in box 2"/>
<TextBox Grid.Row="2" ContextMenu="{StaticResource textBoxMenu}" Tag="{Binding RelativeSource={RelativeSource Self}}" Text="text in box 3"/>
</Grid>
Here is the command code from a ViewModel:
public class MainViewModel : ViewModelBase
{
public ICommand AffirmCommand { get; set; }
public MainViewModel()
{
AffirmCommand = new DelegateCommand<object>(OnAffirmCommand, CanAffirmCommand);
}
private void OnAffirmCommand(object obj)
{
}
private bool CanAffirmCommand(object obj)
{
return true;
}
}

Resources