bind the property to the resource in ResouceDictionary - wpf

My application has some menu buttons, and the button shows the current selection, for example, if user selects "Red", the button image is "Red pen". I prepare XAML files for the button image, like this.
<?xml version="1.0" encoding="utf-8"?>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SmartScreen">
<DrawingBrush x:Key="sample_test" Stretch="Uniform">
<DrawingBrush.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Brush="#FF000000" Geometry="F1 M 87.7748,67.8528L 87.7748,54.9208C 87.7748,52.9101 86.1441,51.2781 84.1375,51.2781L 61.3575,51.2781L 61.3575,24.4635L 63.1001,24.4635C 64.7948,24.4635 66.1788,23.0808 66.1788,21.3861L 66.1788,8.06213C 66.1788,6.36613 64.7948,4.9888 63.1001,4.9888L 49.7748,4.9888C 48.0801,4.9888 46.6988,6.36613 46.6988,8.06213L 46.6988,21.3861C 46.6988,23.0808 48.0801,24.4635 49.7748,24.4635L 51.5095,24.4635L 51.5095,51.2781L 28.7348,51.2781C 27.2241,51.2781 25.9228,52.2061 25.3735,53.5115C 26.0281,53.4501 26.6948,53.4168 27.3601,53.4168C 37.6975,53.4168 46.2041,61.3808 47.0895,71.4915L 84.1375,71.4915C 86.1441,71.4915 87.7748,69.8595 87.7748,67.8528 Z "/>
<GeometryDrawing Brush="#FF000000" Geometry="F1 M 84.138,84.7019C 86.1447,84.7019 87.7753,83.0739 87.7753,81.0672L 87.7753,79.0565C 87.7753,77.0365 86.1447,75.4139 84.138,75.4139L 47.046,75.4139C 46.666,78.8512 45.402,82.0219 43.4873,84.7019L 84.138,84.7019 Z "/>
<GeometryDrawing Brush="#FF000000" Geometry="F1 M 27.3607,85.9973C 20.302,85.9973 14.5847,80.2747 14.5847,73.2253C 14.5847,66.1707 20.302,60.4467 27.3607,60.4467C 34.4193,60.4467 40.134,66.1707 40.134,73.2253C 40.134,80.2747 34.4193,85.9973 27.3607,85.9973 Z M 27.3607,56.9733C 18.386,56.9733 11.114,64.2467 11.114,73.2253C 11.114,82.1933 18.386,89.4693 27.3607,89.4693C 36.3313,89.4693 43.6113,82.1933 43.6113,73.2253C 43.6113,64.2467 36.3313,56.9733 27.3607,56.9733 Z "/>
<GeometryDrawing Brush="{Binding Path=SampleColor}" Geometry="F1 M 35.8893,71.2825L 29.3013,71.2825L 29.3013,63.6785C 29.3013,62.6105 28.4333,61.7465 27.3613,61.7465C 26.288,61.7465 25.4253,62.6105 25.4253,63.6785L 25.4253,72.2132C 25.4253,72.3905 25.4507,72.5559 25.4933,72.7199C 25.4507,72.8785 25.4253,73.0425 25.4253,73.2252C 25.4253,74.2932 26.288,75.1572 27.3613,75.1572L 35.8893,75.1572C 36.964,75.1572 37.8293,74.2932 37.8293,73.2252C 37.8293,72.1532 36.964,71.2825 35.8893,71.2825 Z "/>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</ResourceDictionary>
Put this file (named 'sample_test.xaml') to the ResourceDictionary of Applicaton.Resources.
<Application x:Class="SmartScreen.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="sample_test.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
MainWindow.xaml is following,
<Window x:Class="SmartScreen.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:Test">
<Grid Loaded="Grid_Loaded">
<Label Name="label1" Width="100" Height="100" HorizontalAlignment="Center" VerticalAlignment="Center"
Background="{DynamicResource sample_test}"
/>
<Label Name="label2" Width="100" Height="100" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="100,0">
<Label.Background>
<DrawingBrush Stretch="Uniform">
<DrawingBrush.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Brush="#FF000000" Geometry="F1 M 87.7748,67.8528L 87.7748,54.9208C 87.7748,52.9101 86.1441,51.2781 84.1375,51.2781L 61.3575,51.2781L 61.3575,24.4635L 63.1001,24.4635C 64.7948,24.4635 66.1788,23.0808 66.1788,21.3861L 66.1788,8.06213C 66.1788,6.36613 64.7948,4.9888 63.1001,4.9888L 49.7748,4.9888C 48.0801,4.9888 46.6988,6.36613 46.6988,8.06213L 46.6988,21.3861C 46.6988,23.0808 48.0801,24.4635 49.7748,24.4635L 51.5095,24.4635L 51.5095,51.2781L 28.7348,51.2781C 27.2241,51.2781 25.9228,52.2061 25.3735,53.5115C 26.0281,53.4501 26.6948,53.4168 27.3601,53.4168C 37.6975,53.4168 46.2041,61.3808 47.0895,71.4915L 84.1375,71.4915C 86.1441,71.4915 87.7748,69.8595 87.7748,67.8528 Z "/>
<GeometryDrawing Brush="#FF000000" Geometry="F1 M 84.138,84.7019C 86.1447,84.7019 87.7753,83.0739 87.7753,81.0672L 87.7753,79.0565C 87.7753,77.0365 86.1447,75.4139 84.138,75.4139L 47.046,75.4139C 46.666,78.8512 45.402,82.0219 43.4873,84.7019L 84.138,84.7019 Z "/>
<GeometryDrawing Brush="#FF000000" Geometry="F1 M 27.3607,85.9973C 20.302,85.9973 14.5847,80.2747 14.5847,73.2253C 14.5847,66.1707 20.302,60.4467 27.3607,60.4467C 34.4193,60.4467 40.134,66.1707 40.134,73.2253C 40.134,80.2747 34.4193,85.9973 27.3607,85.9973 Z M 27.3607,56.9733C 18.386,56.9733 11.114,64.2467 11.114,73.2253C 11.114,82.1933 18.386,89.4693 27.3607,89.4693C 36.3313,89.4693 43.6113,82.1933 43.6113,73.2253C 43.6113,64.2467 36.3313,56.9733 27.3607,56.9733 Z "/>
<GeometryDrawing Brush="{Binding SampleColor}" Geometry="F1 M 35.8893,71.2825L 29.3013,71.2825L 29.3013,63.6785C 29.3013,62.6105 28.4333,61.7465 27.3613,61.7465C 26.288,61.7465 25.4253,62.6105 25.4253,63.6785L 25.4253,72.2132C 25.4253,72.3905 25.4507,72.5559 25.4933,72.7199C 25.4507,72.8785 25.4253,73.0425 25.4253,73.2252C 25.4253,74.2932 26.288,75.1572 27.3613,75.1572L 35.8893,75.1572C 36.964,75.1572 37.8293,74.2932 37.8293,73.2252C 37.8293,72.1532 36.964,71.2825 35.8893,71.2825 Z "/>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Label.Background>
</Label>
</Grid>
</Window>
Finnally, the view model of MainWindow should has the property named "SampleColor" like this;
namespace Sample
{
...
public class MainWindowViewModel
{
public Brush SampleColor
{
get
{
return Brushes.Red;
}
}
}
}
Result, the label named "label2" shows the red parts, but "label1" doesn't.
I don't know how to tell the DataContext to the resource in the ResourceDictionary.
This is very simular, but DrawinBrush has no DataContext.

Related

Bind Element to different DataContext property

forgive this question - just learning WPF and it's making my brain hurt. Can't get my head around Bindings/DataContexts. XAML below produced via some code monkery (my knowledge of WPF not good enough atm to know what is helpful to post or not):
<Window x:Class="MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:MoveResize" WindowStartupLocation="CenterScreen"
xmlns:paz="clr-namespace:Wpf.Controls.PanAndZoom;assembly=Wpf.Controls.PanAndZoom"
Title="Move and resize" Height="550" Width="750" Loaded="Window_Loaded" KeyDown="Window_KeyDown" KeyUp="Window_KeyUp">
<Window.Resources>
<!-- Custom Thumb Style-->
<Style x:Key="SliderThumbStyle" TargetType="{x:Type Thumb}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Rectangle x:Name="Rectangle" StrokeThickness="1" Stretch="Fill" Opacity="1">
<Rectangle.Stroke>
<SolidColorBrush Color="Black"></SolidColorBrush>
</Rectangle.Stroke>
<Rectangle.Fill>
<SolidColorBrush Color="Yellow"></SolidColorBrush>
</Rectangle.Fill>
</Rectangle>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- MoveThumb Template -->
<ControlTemplate x:Key="MoveThumbTemplate" TargetType="{x:Type s:MoveThumb}">
<Rectangle Fill="Transparent" />
</ControlTemplate>
<!-- ResizeDecorator Template -->
<ControlTemplate x:Key="ResizeDecoratorTemplate" TargetType="{x:Type Control}">
<Grid>
<!-- Abridged for stackoverflow posting -->
<s:ResizeThumb Style="{StaticResource SliderThumbStyle }" Width="9" Height="9" Cursor="SizeNWSE" VerticalAlignment="Bottom" HorizontalAlignment="Right" />
</Grid>
</ControlTemplate>
<!-- Designer Item Template-->
<ControlTemplate x:Key="DesignerItemTemplate" TargetType="ContentControl">
<Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<s:MoveThumb Template="{StaticResource MoveThumbTemplate}" Cursor="SizeAll" />
<ContentPresenter Content="{TemplateBinding ContentControl.Content}" />
<Control Template="{StaticResource ResizeDecoratorTemplate}" />
</Grid>
</ControlTemplate>
</Window.Resources>
<paz:ZoomBorder x:FieldModifier="public" Name="zoomBorder" Stretch="None" ZoomSpeed="1.2" Background="SlateBlue" ClipToBounds="True" Focusable="True" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Grid.Row="4" Grid.Column="1">
<Canvas x:Name="workspace" Background="#555" Width="1920" Height="1080" Margin="97,93,-1275,-654">
<Path x:Name="YellowDiamond" Fill="Yellow" Data="M 0,5 5,0 10,5 5,10 Z" Stretch="Fill" IsHitTestVisible="True" Canvas.Left="10" Canvas.Top="10" Height="124.75" Width="134.5" />
<ContentControl Width="130" MinWidth="50" Height="130" MinHeight="50" Canvas.Top="150" Canvas.Left="150" Template="{StaticResource DesignerItemTemplate}">
<Path x:Name="BlueDiamond" Fill="Blue" Data="M 0,5 5,0 10,5 5,10 Z" Stretch="Fill" IsHitTestVisible="False" />
</ContentControl>
<TextBox x:Name="widthTB" Height="23" Canvas.Left="131" TextWrapping="Wrap" Text="{Binding BastardWidth, Mode=OneWay}" Canvas.Top="348" Width="120" />
</Canvas>
</paz:ZoomBorder>
</Window>
The key line is:
<s:ResizeThumb Style="{StaticResource SliderThumbStyle }" Width="9" Height="9" Cursor="SizeNWSE" VerticalAlignment="Bottom" HorizontalAlignment="Right" />
I'm wanting to set Width and Height to a property stored in certain class (PanAndZoomController). My understanding is that this is presently databound to ResizeThumb.vb. The relevant code behind:
Partial Public Class MainWindow
Private _panZoomControl As New PanAndZoomController
Public Property PanZoomController() As PanAndZoomController
Get
Return _panZoomControl
End Get
Set(ByVal value As PanAndZoomController)
_panZoomControl = value
End Set
End Property
Public Sub New()
InitializeComponent()
End Sub
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
Me.DataContext = _panZoomControl
End Sub
Private Sub MousewheelMoved(sender As Object, e As MouseWheelEventArgs) Handles Me.MouseWheel
_panZoomControl.ZoomFactor = zoomBorder.ZoomX
End Sub
End Class
Public Class PanAndZoomController
Private _zoomFactor As Double
Public Property ZoomFactor() As Double
Get
Return _zoomFactor
End Get
Set(ByVal value As Double)
_zoomFactor = value
_scaledWidth = 10 * _zoomFactor
End Set
End Property
Private _scaledWidth As Double = 10
Public Property ScaledWidth() As Double
Get
Return _scaledWidth
End Get
Set(ByVal value As Double)
_scaledWidth = value
End Set
End Property
End Class
Thus, Width and Height should be populated from MainWindow.PanZoomController.ScaledWidth. Could someone help out with how to achieve this? In as little XAML as possible, please!? I still need to preserve the binding of that element to s:ResizeThumb due to the code behind.
EDIT: In response to ASh's suggestions.
I've tried NotifyPropertyChanged with what I think is the right way, but still no joy :(
Simplified code scenario. Relevant code behind:
Public Class Workspace
Implements INotifyPropertyChanged
Public Sub NotifyPropertyChanged(ByVal propName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private _canvasZoomFactor As Double = 1
Public Property CanvasZoomFactor() As Double
Get
Return _canvasZoomFactor
End Get
Set(ByVal value As Double)
_canvasZoomFactor = value
_handleWidth = 5 * value
Me.NotifyPropertyChanged("HandleWidth")
End Set
End Property
Private _handleWidth As Double = 5
Public ReadOnly Property HandleWidth() As Double
Get
Return _handleWidth
End Get
End Property
End Class
XAML:
<UserControl x:Name="MainWorkspace" x:Class="Workspace"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:s="clr-namespace:stigzler.utility.WPFcontrols"
xmlns:local="clr-namespace:stigzler.utility.WPFcontrols"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<ControlTemplate x:Key="ResizeDecoratorTemplate" TargetType="{x:Type Control}">
<Grid SnapsToDevicePixels="false">
<s:ResizeThumb Width="{Binding ElementName=Workspace, Path=HandleWidth,UpdateSourceTrigger=PropertyChanged}"
Height="{Binding ElementName=Workspace, Path=HandleWidth,UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource SliderThumbStyle}" Cursor="SizeNWSE" VerticalAlignment="Top"
HorizontalAlignment="Left" />
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="DesignerItemTemplate" TargetType="ContentControl">
<Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<ContentPresenter Content="{TemplateBinding ContentControl.Content}" />
<Control Template="{StaticResource ResizeDecoratorTemplate}" />
</Grid>
</ControlTemplate>
<ContentControl Width="130" MinWidth="50" Height="130" MinHeight="50" Canvas.Top="150" Canvas.Left="150" Template="{StaticResource DesignerItemTemplate}">
<Path x:Name="BlueDiamond" Fill="Blue" Data="M 0,5 5,0 10,5 5,10 Z" Stretch="Fill" IsHitTestVisible="False" />
</ContentControl>
</UserControl>
Relevant lines:
<s:ResizeThumb Width="{Binding ElementName=Workspace, Path=HandleWidth,UpdateSourceTrigger=PropertyChanged}"
Me.NotifyPropertyChanged("HandleWidth")
Still getting no joy. Any other suggestions (I'm still really struggling to get my head around this binding lark).
"My understanding is that this is presently databound to ResizeThumb.vb" - there can't be a binding to a file with code.
Each binding must have a source to get values. There are some options to provide that source:
specify it directly, e.g.: "{Binding Source={StaticResource}}";
use DataContext - the default option: "{Binding Path=SomeProperty}" - when no source is provided, then the binding will try to find requested property in a DataContext - some data associated with the View (or with part of the View - one might want to work with different data in different parts). Usually there is one DataContext per view (Me.DataContext = _panZoomControl)
use other element as binding source (via RelativeSource or ElementName), if the element are in the same view.
s:ResizeThumb is inside MainWindow view, so RelativeSource should work:
<s:ResizeThumb Style="{StaticResource SliderThumbStyle}"
Width="{Binding Path=PanZoomController.ScaledWidth, RelativeSource={RelativeSource AncestorType = {x:Type s:MainWindow}}}"
Height="{Binding Path=PanZoomController.ScaledWidth, RelativeSource={RelativeSource AncestorType = {x:Type s:MainWindow}}}"
hint: when binding doesn't work for some reason, check Output window in Visual Studio - there can be a message describing binding problem (type mismatch, missing property, incorrect DataContext are common reasons)
In addition to #ASh's answer (+1) on how to actually bind to the properties, you should also implement the INotifyPropertyChanged interface in your PanAndZoomController class in order for the view to be refreshed whenever you set the ZoomFactor and ScaledWidth properties dynamically:
Public Class PanAndZoomController
Implements INotifyPropertyChanged
Private _zoomFactor As Double
Public Property ZoomFactor() As Double
Get
Return _zoomFactor
End Get
Set(ByVal value As Double)
_zoomFactor = value
NotifyPropertyChanged(NameOf(ZoomFactor))
ScaledWidth = 10 * _zoomFactor
End Set
End Property
Private _scaledWidth As Double = 10
Public Property ScaledWidth() As Double
Get
Return _scaledWidth
End Get
Set(ByVal value As Double)
_scaledWidth = value
NotifyPropertyChanged(NameOf(ScaledWidth))
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Sub NotifyPropertyChanged(info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
End Class

Read a custom configuration XAML

I have read a XAML file using XAMLReader which refer some custom assemblies. Reading a simple XAML file is easy as I can convert the file to stream and then call XAMLReader.Load().During Xaml load code throws exception for the custom assemblies in xmlns .Below is the code added in a WPF application:
public MainWindow()
{
InitializeComponent();
StreamReader streamReader = new StreamReader(Filepath);
string s = streamReader.ReadToEnd();
var index = s.IndexOf("Canvas");
s = s.Insert(index + 6, "xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"");
var c = XamlReader.Parse(s) as Canvas;
}
and my XAML looks like:
<?xml version="1.0" encoding="utf-8"?>
<Canvas xmlns:IN_SE_BlendObjects_FactoryCast_Shapes_BasicShapes="clr-namespace:IN.SE.BlendObjects.FactoryCast.Shapes.BasicShapes;assembly=IN.SE.BlendObjects.FactoryCast" xmlns:IN_SE_BlendObjects_FactoryCast_Shapes_Extended_Shapes="clr-namespace:IN.SE.BlendObjects.FactoryCast.Shapes.Extended_Shapes;assembly=IN.SE.BlendObjects.FactoryCast" xmlns:IN_SE_BlendObjects_FactoryCast_Control_Animation="clr-namespace:IN.SE.BlendObjects.FactoryCast.Control.Animation;assembly=IN.SE.BlendObjects.FactoryCast" Canvas.Left="0" Canvas.Top="0" Margin="0,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Top">
<Canvas.Background>
<SolidColorBrush Color="#FFFFFFFF" />
</Canvas.Background>
<IN_SE_BlendObjects_FactoryCast_Shapes_Extended_Shapes:FCastLinearGauge Canvas.Left="347.632293701172" Canvas.Top="215.769805908203" Width="328.600006103516" Height="131.440002441406" Margin="0,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" LabelText="" LabelFontFamily="Arial" LabelFontSize="12" IsLabelBold="False" IsLabelItalic="False" ShapeCornerStyle="Rounded" IsBackgroundVisible="True" BorderWidth="0" MajorScaleDivision="2" MinorScaleDivision="5" ScaleContainerType="Rectangle" IsLimitScaleVisible="True" ScalePrecision="1" MajorScaleHeight="15" MinorScaleHeight="8" ScaleValueFontFamily="Arial" ScaleValueSize="13" IsScaleValueBold="False" IsScaleValueItalic="False" IsValueVisible="True" ValueFontSize="12" IsValueBold="False" IsValueItalic="False" ValueTextAlignment="Center" Variable="Name:Variable1,Type:,DefaultValue:" MinEUValue="0" MinValue="0" MaxEUValue="100" MaxValue="100" HHLimitValue="80" HLimitValue="60" LLimitValue="40" LLLimitValue="20">
<IN_SE_BlendObjects_FactoryCast_Shapes_Extended_Shapes:FCastLinearGauge.LabelColor>
<SolidColorBrush Color="#FF000000" />
</IN_SE_BlendObjects_FactoryCast_Shapes_Extended_Shapes:FCastLinearGauge.LabelColor>
<IN_SE_BlendObjects_FactoryCast_Shapes_Extended_Shapes:FCastLinearGauge.BackGroundColor>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0" MappingMode="RelativeToBoundingBox" SpreadMethod="Pad" Opacity="1">
<GradientStop Color="#FF095050" Offset="1" />
<GradientStop Color="#FFA2B8BA" Offset="0.354999989271164" />
</LinearGradientBrush>
</IN_SE_BlendObjects_FactoryCast_Shapes_Extended_Shapes:FCastLinearGauge.BackGroundColor>
</IN_SE_BlendObjects_FactoryCast_Shapes_Extended_Shapes:FCastLinearGauge>
</Canvas>

Draw formatted text within a DrawingImage

I'm trying to include some formatted text as part of a drawing in XAML. Is this possible? How is it done?
Example:
<DrawingImage>
<DrawingImage.Drawing>
<!-- Can text be drawn here? -->
</DrawingImage.Drawing>
</DrawingImage>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,10,10"></RectangleGeometry>
</GeometryDrawing.Geometry>
<GeometryDrawing.Brush>
<VisualBrush>
<VisualBrush.Visual>
<StackPanel>
<TextBlock Text="Tyco" FontSize="16" FontWeight="999" Foreground="Black"></TextBlock>
</StackPanel>
</VisualBrush.Visual>
</VisualBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
Yes. Use a GlyphRunDrawing as part of the DrawingGroup or as the Drawing itself, that is the source of your DrawingImage. To construct the GlyphRun in Xaml is possible, and also in code behind:
Typeface typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretches.Normal);
if (!typeface.TryGetGlyphTypeface(out _glyphTypeface))
return;
_glyphIndexes = new ushort[text.Length];
_advanceWidths = new double[text.Length];
double textWidth = 0;
for (int ix = 0; ix < text.Length; ix++)
{
ushort glyphIndex = _glyphTypeface.CharacterToGlyphMap[text[ix]];
_glyphIndexes[ix] = glyphIndex;
double width = _glyphTypeface.AdvanceWidths[glyphIndex] * FontSize;
_advanceWidths[ix] = width;
textWidth += width;
double textHeight = _glyphTypeface.Height * FontSize;
}

How can you make the color of elements of a WPF DrawingImage dynamic?

We have a need to make certain vector graphic images change the color of certain elements within the graphic at runtime. It would seem that setting those colors based on either static or dynamic resource values wouldn't work. We want to have multiple versions of the same graphic, each with the abilty to set certain graphic elements (Path, Line, etc) to different colors so I don't think that a dynamic resource approach would work. That leaves data binding which seems like the right approach. We update the graphic to use a data binding expr instead of a hard-coded brush color like so:
<DrawingImage x:Key="Graphic1">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Geometry="F1 M 8.4073,23.9233L">
<GeometryDrawing.Pen>
<Pen LineJoin="Round" Brush="{Binding Line1Brush, Mode=OneWay}"/>
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Geometry="F1 M 3.6875,2.56251L">
<GeometryDrawing.Pen>
<Pen LineJoin="Round" Brush="{Binding Line2Brush, Mode=OneWay}"/>
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
Then we create a view model object (supporting INotifyPropertyChanged) instance for each instance of Graphic1 in this case and make sure it has both a Line1Brush and a Line2Brush property. Sounds good, but I can't get it to work. I assume this graphic, which is itself defined in a resource dictionary to Image objects and I attempt to set their DataContext and I get data binding error output in my Debug window. Here's the Image XAML:
<Image x:Name="Pulse1" Grid.Column="0" Source="{StaticResource Graphic1}"/>
<Image x:Name="Pulse2" Grid.Column="1" Source="{StaticResource Graphic1}"/>
And then in the Window's Initialize method I set their data context like so:
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.PulseImage1 = new PulseImageViewModel();
this.PulseImage2 = new PulseImageViewModel();
this.PulseImage2.Line1Brush = Brushes.Green;
this.PulseImage2.Line2Brush = Brushes.Red;
this.Pulse1.DataContext = this.PulseImage1;
this.Pulse2.DataContext = this.PulseImage2;
}
Where PulseImageViewModel (shown below) defines two properties Line1Brush and Line2Brush, each of which fire the PropertyChanged event.
public class PulseImageViewModel : INotifyPropertyChanged
{
private Brush _line1Brush = Brushes.Yellow;
private Brush _line2Brush = Brushes.Black;
public event PropertyChangedEventHandler PropertyChanged;
public Brush Line1Brush
{
get { return _line1Brush; }
set
{
if (_line1Brush != value)
{
_line1Brush = value;
NotifyPropertyChanged("Line1Brush");
}
}
}
public Brush Line2Brush
{
get { return _line2Brush; }
set
{
if (_line2Brush != value)
{
_line2Brush = value;
NotifyPropertyChanged("Line2Brush");
}
}
}
private void NotifyPropertyChanged(string propertyName)
{
var del = PropertyChanged;
if (del != null)
{
del(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Yet I get data binding errors indicating that WPF is looking for Line1Brush on the top level MainWindow object instead of on my PulseImageViewModel object. Any thoughts on what I'm doing wrong or if there's a better way to accomplish my goal of dynamically changeable colors on vector graphics? Also, it would be nice if the graphics could default to a nice, static set of colors if the user didn't hook up the view model object.
The StaticResource is going to have the DataContext from the Window I believe. Does this change if you use DynamicResource?
Edit I get the same results as you, and Snoop is not being helpful. Even moving the DataContext to the XAML changes nothing. For fun I switched to pulling a TextBox into a Label's content, and lo and behold THAT works... No idea what the difference is.
<Window.Resources>
<TextBlock x:Key="Text1"
Text="{Binding Line1Brush}" />
</Window.Resources>
<Grid>
<!-- Confusingly enough, this works -->
<Label Content="{StaticResource Text1}"
DataContext="{Binding PulseImage1}" />
</Grid>
Edit 2 The following works:
<DataTemplate x:Key="Graphic1">
<Image>
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Geometry="F1 M 8.4073,23.9233L">
<GeometryDrawing.Pen>
<Pen LineJoin="Round"
Brush="{Binding Line1Brush, Mode=OneWay}" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Geometry="F1 M 3.6875,2.56251L">
<GeometryDrawing.Pen>
<Pen LineJoin="Round"
Brush="{Binding Line2Brush, Mode=OneWay}" />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</DataTemplate>
With the XAML looking like:
<ContentPresenter ContentTemplate="{StaticResource Graphic1}"
Content="{Binding PulseImage1}"
Grid.Column="0" />
<ContentPresenter ContentTemplate="{StaticResource Graphic1}"
Content="{Binding PulseImage2}"
Grid.Column="1" />

Custom button with property as StaticResource

I am trying to achieve the following thing: use an svg image into a custom button.
In order to do this I created a Custom button:
public class MainButton : Button
{
static MainButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MainButton),
new FrameworkPropertyMetadata(typeof(MainButton)));
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MainButton),
new UIPropertyMetadata(""));
public object Image
{
get { return (object)GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
public static readonly DependencyProperty ImageProperty =
DependencyProperty.Register("Image", typeof(object), typeof(MainButton),
new UIPropertyMetadata(""));
}
I took a svg file, opened it in inkscape and saved it as xaml file.
I opened Themes.xaml and added the created xaml image as a ControlTemplate
<ControlTemplate x:Key="Home">
<Viewbox Stretch="Uniform">
<Canvas .....
</Canvas>
</Viewbox>
</ControlTemplate>
And the button style is:
Style TargetType="{x:Type local:MainButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MainButton}">
<Canvas x:Name="sp" Width="80"
Height="80">
<StackPanel Canvas.Top="12"
Canvas.Left="0"
Canvas.ZIndex="2"
Width="80">
<ContentControl x:Name="Img" Template="{StaticResource Home}" />
</StackPanel>
<StackPanel x:Name="spText" Canvas.Top="45"
Canvas.Left="1"
Canvas.ZIndex="1"
Width="80">
<TextBlock x:Name="Txt" Text="{Binding Path=(local:MainButton.Text),
RelativeSource ={RelativeSource FindAncestor,
AncestorType ={x:Type Button}}}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Foreground="White"
FontSize="14"/>
</StackPanel>
...
As you can see I have hardcoded the StaticResource name <ContentControl x:Name="Img" Template="{StaticResource Home}" />
I want to be able to have a binding with property Image on this Template, something like
<ContentControl x:Name="Img" Template="{StaticResource {???Binding Image???}}" />
So that I can set the Image property of the button with the name of the StaticResource I want.
For example, having beside "Home" image, another one "Back" I would have two buttons in MainWindow declared like this:
<MyControls:MainButton Text="Go Home!" Image="Home" />
<MyControls:MainButton Text="Go Back!" Image="Back" />
Any advice is kindly taken. Thank you for your time.
You can achieve the same result without creating a custom button. For example, in the following code, I created an image into the grid resource and then I use it inside the button's content.
<Grid>
<Grid.Resources>
<DrawingImage x:Key="Home">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Brush="#FFFFFFFF" Geometry="F1 M 0,20L 30,20L 30,40L 0,40 Z ">
<GeometryDrawing.Pen>
<Pen Thickness="2" LineJoin="Round" Brush="#FF000000"/>
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Grid.Resources>
<Button>
<StackPanel Orientation="Horizontal">
<Image Source="{StaticResource ResourceKey=Home}" />
<TextBlock Text="Hello!" />
</StackPanel>
</Button>
</Grid>
To add more images, you can simply add another DrawingImage into the grid resource and then use it in the same way into another button.

Resources