WPF Image with two sources - c#

I'd like to extend Image class by adding second source. I want to define second source in XAML (like original source) and change these images when mouse enters/leaves this image.
I tried myself with:
class MainMenuImageButton : Image
{
public static readonly DependencyProperty Source2Property;
public ImageSource Source2
{
get { return Source2; }
set
{
this.MouseEnter+=new System.Windows.Input.MouseEventHandler(MainMenuImageButton_MouseEnter);
}
}
public void MainMenuImageButton_MouseEnter(object sender, MouseEventArgs e)
{
this.Source = Source2;
}
}
But it doesn't work and I think I do it tottaly wrong. Can somebody help?
[UPDATE]
I wrote this:
class MainMenuImageButton : Image
{
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
var source = (BitmapSource)Source;
var x = (int)(hitTestParameters.HitPoint.X / ActualWidth * source.PixelWidth);
var y = (int)(hitTestParameters.HitPoint.Y / ActualHeight * source.PixelHeight);
var pixels = new byte[4];
source.CopyPixels(new Int32Rect(x, y, 1, 1), pixels, 4, 0);
if (pixels[3] < 10) return null;
return new PointHitTestResult(this, hitTestParameters.HitPoint);
}
public ImageSource Source1
{
get { return GetValue(ImageSourceProperty) as ImageSource; }
set { base.SetValue(ImageSourceProperty, value); }
}
public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register("Source1", typeof(ImageSource), typeof(MainMenuImageButton));
public ImageSource Source2
{
get { return GetValue(ImageSource2Property) as ImageSource; }
set { base.SetValue(ImageSource2Property, value); }
}
public static readonly DependencyProperty ImageSource2Property = DependencyProperty.Register("Source2", typeof(ImageSource), typeof(MainMenuImageButton));
public MainMenuImageButton() : base()
{
this.MouseEnter += new MouseEventHandler(MainMenuImageButton_MouseEnter);
this.MouseLeave += new MouseEventHandler(MainMenuImageButton_MouseLeave);
}
void MainMenuImageButton_MouseLeave(object sender, MouseEventArgs e)
{
this.Source = this.Source1;
}
void MainMenuImageButton_MouseEnter(object sender, MouseEventArgs e)
{
this.Source = this.Source2;
}
}
But sometimes it works and sometimes there is exception: "An unhandled exception of type 'System.ArgumentException' occurred in PresentationCore.dll
Additional information: The value is outside the expected range."
I'm not sure if I understood, but I tried this:
class MainMenuImageButton : Image
{
public static readonly DependencyProperty Source2Property = DependencyProperty.Register("Source2", typeof(ImageSource), typeof(MainMenuImageButton), new PropertyMetadata(true));
public ImageSource Source2
{
get { return (ImageSource)GetValue(Source2Property); }
set
{
BitmapImage logo = new BitmapImage(new Uri(value.ToString(), UriKind.Relative));
SetValue(Source2Property, logo);
this.MouseEnter+=new System.Windows.Input.MouseEventHandler(MainMenuImageButton_MouseEnter);
}
}
public void MainMenuImageButton_MouseEnter(object sender, MouseEventArgs e)
{
this.Source = Source2;
}
}
And still nothing. Wham am I doing wrong?

Refer to the Custom Dependency Properties article on MSDN. The event hookup belongs in your dependency property's PropertyChangedCallback.
I would also suggest using a trigger instead of event handling. However, this doesn't mean you will need to duplicate the XAML everywhere you want to use it. You could define a custom control with the image switching trigger in its default style (see "Defining Resources at the Theme Level" in the Control Authoring Overview). Where MouseOverImage is a Control with "Source" and "Source2" dependency properties, you could define this default style:
<Style TargetType="local:MouseOverImage">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MouseOverImage">
<Grid>
<Image Name="SourceImage" Source="{TemplateBinding Source}" />
<Image Name="Source2Image" Source="{TemplateBinding Source2}" Visibility="Hidden" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="SourceImage" Property="Visibility" Value="Hidden" />
<Setter TargetName="Source2Image" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
If you use event handlers, you would need to store the original value of Source, add a MouseLeave handler that reverts it, and also consider the case where a user reassigns Source or Source2 at any time. Using the trigger solution with two separate "Source" and "Source2" bindings, all of this is handled automatically.
EDIT
But sometimes it works and sometimes there is exception: "An unhandled
exception of type 'System.ArgumentException' occurred in
PresentationCore.dll
Additional information: The value is outside the expected range."
My guess is that HitTestCore is firing after the source changes but before it's applied to the layout, so there is a discrepancy between ActualWidth and source.PixelWidth. I am not sure of the rationale for including these in the calculation (shouldn't they always be the same?) Try just using the following:
var x = (int)hitTestParameters.HitPoint.X;
var y = (int)hitTestParameters.HitPoint.Y;

Extending Image Is an overkill, all you have to do is define a style which will use trigger to swap the sources
<Image>
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="Image1"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Source" Value="Image2"/>
</Trigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>

You don't need to extend the Image class to do this. There is a property on the Image class called IsMouseOver that you can trigger on to switch the Source of your image. Put this in a style on your view and you'll be all set.

You need to add the new property as a Dependency Property. You can find out more from the DependencyProperties Overview page at MSDN, but the basic idea is this:
You first create the Dependency Property:
public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register(
"IsSpinning", typeof(Boolean), typeof(YourClassName), new PropertyMetadata(true));
Then you can optionally add a wrapper using standard CLR properties (for your own use only):
public bool IsSpinning
{
get { return (bool)GetValue(IsSpinningProperty); }
set { SetValue(IsSpinningProperty, value); }
}
(Code example was taken from the linked article)

Related

XDG0008 error occuring while implementing DependencyProperty into TemplateBinding [duplicate]

This question already has answers here:
Template Binding with Attached Properties
(2 answers)
Closed 1 year ago.
To easily change the template-specific brushes of a button without directly changing the template, I decided to make a DependencyProperty that will bind to a template-specific brush. That way, I can change this brush just as easy as changing any other regular property. However, after implementing this DependencyProperty, I encountered an error: "Name "ExtensionClass" does not exist in namespace "clr-namespace:extensions"." What causes this error?
XAML:
<ResourceDictionary xmlns:ext="clr-namespace:Extensions"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<ControlTemplate x:Key="ButtonBaseControlTemplate1" TargetType="{x:Type ButtonBase}">
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" TargetName="border" Value="{TemplateBinding Property=ext:ExtensionsClass.MouseOverBackground}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ResourceDictionary>
C#:
namespace Extensions {
public class ExtensionsClass {
public static readonly DependencyProperty MouseOverBackgroundProperty = DependencyProperty.Register("MouseOverBackground", typeof(Brush), typeof(Button));
public static void SetMouseOverBackground(UIElement element, Brush value) {
element.SetValue(MouseOverBackgroundProperty, value);
}
public static Brush GetMouseOverBackground(UIElement element) {
return (Brush)element.GetValue(MouseOverBackgroundProperty);
}
}
}
In addition to the problem with the Binding, which is covered in the answer to the duplicate question, you also have to be aware that you are declaring an attached property, which has to be registered with the RegisterAttached method.
Besides that, in both the Register and the RegisterAttached methods, the third argument has to be the type that declares the property, not the type of element where you intend to set the property, i.e. typeof(ExtensionsClass) here.
public static class ExtensionsClass
{
public static readonly DependencyProperty MouseOverBackgroundProperty =
DependencyProperty.RegisterAttached(
"MouseOverBackground",
typeof(Brush),
typeof(ExtensionsClass),
null);
public static void SetMouseOverBackground(UIElement element, Brush value)
{
element.SetValue(MouseOverBackgroundProperty, value);
}
public static Brush GetMouseOverBackground(UIElement element)
{
return (Brush)element.GetValue(MouseOverBackgroundProperty);
}
}
You bind to an attached property by means of a Binding Path with parentheses:
<Setter
Property="Background"
TargetName="border"
Value="{Binding Path=(ext:ExtensionsClass.MouseOverBackground),
RelativeSource={RelativeSource TemplatedParent}}"/>

WPF: Parent which would display a specific child depending on input value

As far as I understand, usual Visibility={Binding SomeValue, Converter={…}} still keeps node in visual and logical tree even if it’s invisible. But what if I want to remove it completely and at the same time keep syntax light?
Right now, I’ve made a class called Switch allowing me to do stuff like that:
<Switch Value="{Binding Status}">
<TextBlock Switch.When="{x:Static Status.NotFound}" Text="Not found" />
<Button Switch.When="{x:Static Status.ConnectionError}" Text="Connection error. Try again?" />
<Grid Switch.When="{x:Static Status.Loaded}">…</Grid>
</Switch>
Here is a source code of that Switch thing.
I like how it looks and works, but sometimes some errors occur. For instance, while removing visual child from its previous location, rarely, but System.InvalidOperationException: Cannot modify the logical children for this node at this time because a tree walk is in progress. might happen. Cases like this are solvable, but the whole thing with them makes me think I’m doing something very wrong. What could it be? Maybe the whole idea is just not compatible with WPF at all? Or maybe I’m just missing something (like that thing that I have to override IEnumerator LogicalChildren { get; } to make it work properly)?
I think the answer is probably that you're trying to reparent the child controls the right way. Bad idea! They're just arbitrary content; don't treat them as controls. The actual reparenting in my code is done by hidden ContentPresenter magic in the template. All our control class code does is just sling them around like potatoes.
Here's a working version of the control that shouldn't give you any backtalk. But note that I had trouble comparing boxed enum values to each other. I'm interested in how you solved that problem.
Switch.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
namespace SwitchTestProject
{
[ContentProperty("Items")]
public class Switch : Control
{
public Switch()
{
Items = new List<DependencyObject>();
}
static Switch()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Switch), new FrameworkPropertyMetadata(typeof(Switch)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
OnValueChanged(null);
}
#region Switch.When Attached Property
public static Object GetWhen(DependencyObject obj)
{
return (Object)obj.GetValue(WhenProperty);
}
public static void SetWhen(DependencyObject obj, Object value)
{
obj.SetValue(WhenProperty, value);
}
public static readonly DependencyProperty WhenProperty =
DependencyProperty.RegisterAttached("When", typeof(Object), typeof(Switch),
new PropertyMetadata(null));
#endregion Switch.When Attached Property
#region Content Property
public Object Content
{
get { return (Object)GetValue(ContentProperty); }
protected set { SetValue(ContentPropertyKey, value); }
}
internal static readonly DependencyPropertyKey ContentPropertyKey =
DependencyProperty.RegisterReadOnly(nameof(Content), typeof(Object), typeof(Switch),
new PropertyMetadata(null));
public static readonly DependencyProperty ContentProperty = ContentPropertyKey.DependencyProperty;
#endregion Content Property
#region Value Property
public Object Value
{
get { return (Object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(Object), typeof(Switch),
new FrameworkPropertyMetadata(null, Value_PropertyChanged));
protected static void Value_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as Switch).OnValueChanged(e.OldValue);
}
private void OnValueChanged(object oldValue)
{
if (Value is IComparable)
{
// Boxed value types have to be a special case.
// Unless I jumped to an unwarranted conclusion about == not working.
var icompval = Value as IComparable;
foreach (var item in Items)
{
var icompwhen = GetWhen(item) as IComparable;
if (icompwhen != null && icompval.CompareTo(icompwhen) == 0)
{
Content = item;
return;
}
}
}
else
{
Content = Items.FirstOrDefault(item => GetWhen(item) == Value);
}
}
#endregion Value Property
#region Items Property
public List<DependencyObject> Items
{
get { return (List<DependencyObject>)GetValue(ItemsProperty); }
protected set { SetValue(ItemsPropertyKey, value); }
}
internal static readonly DependencyPropertyKey ItemsPropertyKey =
DependencyProperty.RegisterReadOnly(nameof(Items), typeof(List<DependencyObject>), typeof(Switch),
new PropertyMetadata(null));
public static readonly DependencyProperty ItemsProperty = ItemsPropertyKey.DependencyProperty;
#endregion Items Property
}
}
App.xaml or Themes\Generic.xaml
You could do a lot more with styling the parent here.
<Style TargetType="local:Switch">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Switch">
<ContentPresenter
/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Usage:
<local:Switch
Value="{Binding Status}"
>
<TextBlock
local:Switch.When="{x:Static local:Status.NotFound}"
>This is a test</TextBlock>
<TextBlock
local:Switch.When="{x:Static local:Status.ConnectionError}"
>There was an error in the connection</TextBlock>
</local:Switch>
Pure XAML alternative
The bug in your Switch control is probably fixable, but this will work reliably without any nonsense (other than all the verbosity).
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="NotFound">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Not found" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="ConnectionError">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Button Content="Connection error. Try again?" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="Loaded">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Ellipse
Height="32"
Width="32"
Fill="DeepSkyBlue"
/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
We set the content using DataTemplates rather than setting the Content property directly because if we use the latter method, only one instance of each child control will ever exist, so we wouldn't be able to factor the style out as a resource and reuse it.
And because templating is the canonical way to create new controls in XAML.

Update Normal Property in Dependency property/AttachedProperty,

I am trying to bind a normal property of AvalonDock,
xmlns:xcad="http://schemas.xceed.com/wpf/xaml/avalondock"
<xcad:LayoutAnchorable Title="Folder" CanHide="{Binding IsHideExplorerView}">
<Views:ExplorerView DataContext="{Binding ExplorerViewModel}"/>
</xcad:LayoutAnchorable>
Here CanHide is a Normal property, if trying to bind will throw the exception like
A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
My question is, Is it possible any way to make a normal property to override DependencyProperty to make it Bindable.
Edit
Added a class which inherit LayoutAnchorable but PropertyChangedCallback of DependencyProperty Never calls.
public class ExtendedAnchorableItem : LayoutAnchorable
{
public static readonly DependencyProperty IsCanHideProperty =
DependencyProperty.RegisterAttached("IsCanHide", typeof(bool), typeof(ExtendedAnchorableItem),
new FrameworkPropertyMetadata((bool)false,
new PropertyChangedCallback(OnCanHideChanged)));
public bool IsCanHide
{
get { return (bool)GetValue(IsCanHideProperty); }
set { SetValue(IsCanHideProperty, value);
this.IsVisible = value; // No effect.
}
}
private static void OnCanHideChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ExtendedAnchorableItem)d).Hide();
}
}
XAML
<xcad:LayoutAnchorablePane>
<Utility:ExtendedAnchorableItem IsCanHide="{Binding IsHideExplorer}">
<Views:ExplorerView DataContext="{Binding ExplorerViewModel}"/>
</Utility:ExtendedAnchorableItem>
</xcad:LayoutAnchorablePane>
Similarly i have tried creating an AttachedProperty which can hook it to LayoutAnchorable but PropertyChangedCallback Never get called click here for a new question i have posted.
Any Help guys ?
I did and example previously in my case i need to create new button with 2 images one when the button is available and the other one when it's disabled, to do that first i created new user control named "MyButton" my xaml was like this
<Button ToolTip="{Binding ButtonLabel,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"
Command="{Binding ButtonCommand,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"
Cursor="Hand" VerticalAlignment="Center" >
<Button.Template>
<ControlTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="45"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Name="ButtonImage" IsEnabled="{Binding Path=IsEnabled,RelativeSource={RelativeSource AncestorType=Button,Mode=FindAncestor}}" >
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Source" Value="{Binding ActiveImage,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Source" Value="{Binding DeactiveImage,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"/>
</Trigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Label Name="LabelContent" Content="{Binding ButtonLabel,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"
Grid.Column="1" IsEnabled="{Binding Path=IsEnabled,RelativeSource={RelativeSource AncestorType=Button,Mode=FindAncestor}}" VerticalContentAlignment="Center" />
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
then i added dependency Properties for ActiveImage and DeactiveImage using this code
public static DependencyProperty activeImage =
DependencyProperty.Register("ActiveImage", typeof(type of this property like "string"), typeof(type of the custom control that you need like "MyButton"), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string ActiveImage
{
get { return (string)GetValue(activeImage); }
set { SetValue(activeImage, value); }
}
then i used this new control in my project
<custom:MyButton ButtonCommand="{Binding DecreaseImagesCount}" ButtonLabel="ZoomIn" ActiveImage="/Images/ActiveImages/ZoomIn.png" DeactiveImage="/Images/GrayImages/ZoomIn.png"
Grid.Column="2" Margin="3,4" />
notice that i can do binding the path for Button Image now
If it is enough for you to just set that property from your view model then you could use an attached behavior.
Just create a new class and add an attached property like this (I did not really test this, since I actually do not have AvalonDock at hand, but you should get the idea):
public class YourBehavior
{
public static readonly DependencyProperty YourCanHideProperty = DependencyProperty.RegisterAttached(
"YourCanHide",
typeof(bool),
typeof(LayoutAnchorable),
new PropertyMetadata(YourCanHidePropertyChanged));
private static void YourCanHidePropertyChanged(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
LayoutAnchorable control = dependencyObject as LayoutAnchorable;
if (control != null)
{
control.CanHide = e.NewValue as bool;
}
}
public static bool GetYourCanHideProperty(LayoutAnchorablewindow)
{
return window.GetValue(YourProperty) as bool?;
}
public static void SetYourCanHideProperty(LayoutAnchorable control, bool value)
{
window.SetValue(YourProperty, value);
}
}
Now you should be able to use that behavior like this:
<xcad:LayoutAnchorable Title="Folder" namespacealias:YourBehavior.YourCanHideProperty="{Binding IsHideExplorerView}"/>
If you want to have it working in both directions just check out the attached Blend behaviors.
Yes, you can do it.. you need to implement INotifypropertyChanged interface and raise a ProprtyChanged Event inside the property setter. After changing the property to a DependencyProperty, you will get the notification mechanism, so the property change is propagated to the target (in this case xcad) .
you can find lot of examples implementing the INotifyPropertyChanged..

change setter value in style

I'm programming in WPF(c#). I'm trying to change value in a setter of style.
my style is:
<Style TargetType="Control" x:Key="st">
<Setter Property="FontFamily" Value="Tahoma"/>
<Setter Property="FontSize" Value="14"/>
</Style>
and I use it in a button:
<Button x:Name="btnCancel" Style="{StaticResource st}" Content="انصراف" Canvas.Left="30" Canvas.Top="18" Width="139" Height="53" FontFamily="2 badr" FlowDirection="LeftToRight" Click="btnCancel_Click_1" />
and what I try to do is this code:
Style style = new Style();
style = (Style) Resources["st"];
Setter setter =(Setter) style.Setters[1];
setter.Value = 30;
after setting font size to 30 I get this error?
After a “SetterCollectionBase” is in use (sealed), it cannot be modified
How can I solve this problem?
The styles can be set only once (sealed after compiling), you can't change it with code
so the solutions are
create a style by code
Style st = new Style(typeof(System.Windows.Controls.Control));
st.Setters.Add(new Setter(Control.FontFamilyProperty, new FontFamily("Tahoma")));
st.Setters.Add(new Setter(Control.FontSizeProperty, 14.0));
later you can change it
st.Setters.OfType<Setter>().FirstOrDefault(X => X.Property == Control.FontSizeProperty).Value = 30.0;//safer than Setters[1]
or
change the property directly
btnCancel.FontSize=30.0;
Since you are doing pure UI and behind the code while some answers recommend you to use MVVM which will really make a lot of things easier.
Why do you need to manipulate the Style? Is it just for the button and you want to manipulate its FontSize? I assume you are doing this on the Click event of the button where it changes the fontsize.
Try this then
private void btnCancel_Click_1(object sender, RoutedEventArgs e)
{
var button = sender as Button;
if (button != null) button.FontSize = 30;
}
You'll need to create a view model, something like this (I'm using the MVVM Lite class ViewModelBase, you just need something that supports property change notification):
public class MyViewModel : ViewModelBase
{
private double _FontSize = 0.0;
public double FontSize
{
get { return this._FontSize; }
set { this._FontSize = value; RaisePropertyChanged(() => this.FontSize); }
}
}
Then create an instance of it in your window along with a getter:
public partial class Window1 : Window
{
public MyViewModel MyViewModel {get; set;}
public Window1()
{
InitializeComponent();
this.MyViewModel = new MyViewModel { FontSize = 80 };
}
}
And then finally you need to bind your style to use the value in the view model:
<Window.Resources>
<Style TargetType="Control" x:Key="st">
<Setter Property="FontFamily" Value="Tahoma"/>
<Setter Property="FontSize" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}, Path=MyViewModel.FontSize}"/>
</Style>
</Window.Resources>

Saving a WPF canvas as an image following MVVM Pattern

I have a canvas, e.g. similar to this solution or many others using the ItemsControl.
Now I want a button which should be bound to an ICommand. This command should call a method of ViewModel class which can save the image.
The saving method is clear, but how do I do the binding following the MVVM pattern?
You could pass the Canvas to the ViewModel's Save method using a CommandParameter
<Button Content="Save"
Command="{Binding SaveCanvasCommand}"
CommandParameter="{Binding ElenementName=myCanvas}" ?>
<Canvas x:Name="myCanvas">
<!-- Stuff to save -->
</Canvas>
And somewhere in you ViewModel or Command you'd have
void SaveCanvasCommandExecute(object parameter)
{
UIElement toSave = (UIElement)parameter;
//.. You'd probably use RenderTargetBitmap here to save toSave.
}
If you don't want to reference UI elements in your ViewModel you could use an attached behaviour:
internal static class Behaviours
{
public static readonly DependencyProperty SaveCanvasProperty =
DependencyProperty.RegisterAttached("SaveCanvas", typeof(bool), typeof(Behaviours),
new UIPropertyMetadata(false, OnSaveCanvas));
public static void SetSaveCanvas(DependencyObject obj, bool value)
{
obj.SetValue(SaveCanvasProperty, value);
}
public static bool GetSaveCanvas(DependencyObject obj)
{
return (bool)obj.GetValue(SaveCanvasProperty);
}
private static void OnSaveCanvas(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
// Save code.....
}
}
}
Then in your ViewModel you have your Command that sets a property, also on your ViewModel:
public ICommand SaveCanvasCommand
{
get
{
if (_saveCanvasCommand == null)
_saveCanvasCommand = new RelayCommand(() => { IsSaveCanvas = true; });
return _saveCanvasCommand;
}
}
And the property which is bound to your View:
public bool IsSaveCanvas
{
get { return _isSaveCanvas; }
set
{
_isSaveCanvas = value;
RaisePropertyChanged("IsSaveCanvas");
}
}
Then hooking it all up in the Xaml looks like this:
Add a Trigger on the Control that binds the value of your ViewModel property to your attached behaviour:
<UserControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSaveCanvas}" Value="True">
<Setter Property="wpfApplication1:Behaviours.SaveCanvas" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSaveCanvas}" Value="False">
<Setter Property="wpfApplication1:Behaviours.SaveCanvas" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
And then bind your Button / MenuItem to the ViewModels Save Command:
<Canvas.ContextMenu>
<MenuItem Header="Save" Command="{Binding SaveCanvasCommand}"/>
</Canvas.ContextMenu>

Categories