I need to fit the VisualBrush used into the button to the entire window. The VisualBrush is linked to an Image that is stretched to the entire visualization, but in the visual that image starts to appear in the corner of the button.
<Button x:Name="button" HorizontalAlignment="Center" VerticalAlignment="Center" Content="Acquista ora- $23.99" FontSize="48" BorderBrush="{x:Null}">
<Button.Background>
<VisualBrush Visual="{Binding ElementName=img}" Stretch="None" AlignmentX="Center" AlignmentY="Center" ViewboxUnits="RelativeToBoundingBox" ViewportUnits="RelativeToBoundingBox" />
</Button.Background>
</Button>
How can I do? Thanks in advance.
If want to blur the image behind the Button (or a transparent control in general) you have to follow a different approach.
You need the exact tile of the image in order to blur it using the BlurEffect.
In order to not blur the Button itself, you must add alayer beneath the button that has the BlurEffect applied.
The following example extends a ContentControl named BlurHost that renders the Content e.g., the Button, on top of a Border element that will actualy blur the background using a VisualBrush.
The brush itself has a tile defined that is located at the position of the BlurHost which hosts the Button (or any other transparent control).
The basic steps to implement a blurred background:
Add the background image
Create a blur layer beneath the element
Get the bounds of the element e.g., the Button which is located relative to the parent of the Image (preferably the root container)
Use the bounding rectangle to define the tile of the VisualBrush (the actual section of the image)
Apply the brush on the blur layer
Usage example
MainWindow.xaml
<Window>
<!-- Allow the root grid to stretch accross the Window -->
<Grid>
<Image x:Name="img" Source="/someImage.png" />
<!--
Optionally override the default BlurEffect
by setting the BlurHost.BlurEffect property
-->
<local:BlurHost BlurBackground="{Binding ElementName=img}"
BlurOpacity="1"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Button Background="Transparent"
FontSize="48"
Content="Acquista ora- $23.99" />
</local:BlurHost>
</Grid>
</Window>
Implementation example
The implementation is simple. You have to add property changed handlers in order to make the control dynamic.
BlurHost.cs
The ContentControl serves as a container. The blurred background is visible at the transparent areas of the content.
public class BlurHost : ContentControl
{
public Visual BlurBackground
{
get => (Visual)GetValue(BlurBackgroundProperty);
set => SetValue(BlurBackgroundProperty, value);
}
public static readonly DependencyProperty BlurBackgroundProperty =
DependencyProperty.Register(
"BlurBackground",
typeof(Visual),
typeof(BlurHost),
new PropertyMetadata(default(Visual), OnBlurBackgroundChanged));
public double BlurOpacity
{
get => (double)GetValue(BlurOpacityProperty);
set => SetValue(BlurOpacityProperty, value);
}
public static readonly DependencyProperty BlurOpacityProperty =
DependencyProperty.Register(
"BlurOpacity",
typeof(double),
typeof(BlurHost),
new PropertyMetadata(1.0));
public BlurEffect BlurEffect
{
get => (BlurEffect)GetValue(BlurEffectProperty);
set => SetValue(BlurEffectProperty, value);
}
public static readonly DependencyProperty BlurEffectProperty =
DependencyProperty.Register(
"BlurEffect",
typeof(BlurEffect),
typeof(BlurHost),
new PropertyMetadata(
new BlurEffect()
{
Radius = 10,
KernelType = KernelType.Gaussian,
RenderingBias = RenderingBias.Performance
}));
private Border PART_BlurDecorator { get; set; }
private VisualBrush BlurDecoratorBrush { get; set; }
static BlurHost()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BlurHost), new FrameworkPropertyMetadata(typeof(BlurHost)));
}
public BlurHost()
{
Loaded += OnLoaded;
// TODO::Update Opacity of VisualBrush when property BlurOpacity changes
this.BlurDecoratorBrush = new VisualBrush()
{
ViewboxUnits = BrushMappingMode.Absolute,
Opacity = this.BlurOpacity
};
}
private void DrawBlurredElementBackground()
{
if (!TryFindVisualRootContainer(this, out FrameworkElement rootContainer))
{
return;
}
// Get the section of the image where the BlurHost element is located
Rect elementBounds = TransformToVisual(rootContainer)
.TransformBounds(new Rect(this.RenderSize));
// Use the section bounds to actually "cut out" the image tile
this.BlurDecoratorBrush.Viewbox = elementBounds;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (TryFindVisualRootContainer(this, out FrameworkElement rootContainer))
{
rootContainer.SizeChanged += OnRootContainerElementResized;
}
DrawBlurredElementBackground();
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.PART_BlurDecorator = GetTemplateChild("PART_BlurDecorator") as Border;
this.PART_BlurDecorator.Effect = this.BlurEffect;
this.PART_BlurDecorator.Background = this.BlurDecoratorBrush;
}
private static void OnBlurBackgroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var this_ = d as BlurHost;
this_.BlurDecoratorBrush.Visual = e.NewValue as Visual;
this_.DrawBlurredElementBackground();
}
private void OnRootContainerElementResized(object sender, SizeChangedEventArgs e)
=> DrawBlurredElementBackground();
private bool TryFindVisualRootContainer(DependencyObject child, out FrameworkElement rootContainerElement)
{
rootContainerElement = null;
DependencyObject parent = VisualTreeHelper.GetParent(child);
if (parent == null)
{
return false;
}
if (parent is not Window visualRoot)
{
return TryFindVisualRootContainer(parent, out rootContainerElement);
}
rootContainerElement = visualRoot.Content as FrameworkElement;
return true;
}
}
Generic.xaml
The default Style for the BlurHost. The Generic.xaml file is located in the Themes folder of the application (project).
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Net.Wpf">
<Style TargetType="local:BlurHost">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:BlurHost">
<Grid>
<!-- Blur layer beneath the hosted element (ContentPresenter) -->
<Border x:Name="PART_BlurDecorator"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"/>
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Related
I want to create an animation of a bar (rectangle) going from its current width to 0 which will be used as visualization of a countdown.
In the end it should look somehow like this:
Right now my trigger to start the animation is set in a static class (this part already works).
<Control x:Name="content" Grid.Column="0" Margin="0">
<Control.Template>
<ControlTemplate>
<Grid x:Name="FilledCountdownBar" Width="500" HorizontalAlignment="Left" >
<Rectangle Fill="#FFA4B5BF"/>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=(Managers:ActionModeManager.ShowUiTimer)}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="FilledCountdownBar"
Storyboard.TargetProperty="(FrameworkElement.Width)"
To="0" Duration="0:1:0" AutoReverse="False"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Control.Template>
</Control>
I got several points which I do not get to work here:
This view will be located in the bottom of a window, which is scaleable. So I do not know the starting width in pixels at the beginning of the animation. I would love to remove the "Width" from the FilledCountdownBar element to let it fill the whole space at the start automatically, but then I cannot animate that value (getting an exception).
When I do not set the "From" property of the animation, then the animation does not reset because there is no start value and the width will remain 0 after the animation finished playing the first time.
I have a property (Duration type) in my viewmodel which I want to bind to the duration of the animation. But it looks like I cannot do that in control templates? An exception is thrown:
Cannot freeze this Storyboard timeline tree for use across threads.
I also tried to use a ProgressBar instead, but I could not get it animating smoothly. There were always small steps visible when changing the value, so like that it is not really an option for me.
Any help is welcome, thanks in advance.
When I need to have dynamic animations that rely on Widths and things like this, I always do them in code as attached behaviors or in custom control code.
This allows you to create a Storyboard in code, set all its dynamic properties and then start it.
In this case, once the animation kicks off, it will be for the size of the control once it starts. If the user resizes the window while it's running, the animation won't dynamically scale itself. However, you can indeed make that happen. I just implemented your simple DoubleAnimation.
Here is a working example for your case:
XAML
<Window x:Class="WpfApp4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp4"
Title="MainWindow"
Width="800"
Height="450"
UseLayoutRounding="True">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Control x:Name="CountDownVisual"
Grid.Row="1"
Height="30"
Margin="0">
<Control.Template>
<ControlTemplate>
<Grid x:Name="RootElement">
<Grid x:Name="CountDownBarRootElement"
local:CountDownBarAnimationBehavior.IsActive="{Binding ShowUiTimer}"
local:CountDownBarAnimationBehavior.ParentElement="{Binding ElementName=RootElement}"
local:CountDownBarAnimationBehavior.TargetElement="{Binding ElementName=CountDownBar}">
<Rectangle x:Name="CountDownBar"
HorizontalAlignment="Left"
Fill="#FFA4B5BF" />
</Grid>
</Grid>
</ControlTemplate>
</Control.Template>
</Control>
</Grid>
</Window>
Attached Behavior
using System;
using System.Windows;
using System.Windows.Media.Animation;
using System.Windows.Threading;
namespace WpfApp4
{
public static class CountDownBarAnimationBehavior
{
private static Storyboard sb;
#region IsActive (DependencyProperty)
public static readonly DependencyProperty IsActiveProperty = DependencyProperty.RegisterAttached("IsActive", typeof(bool), typeof(CountDownBarAnimationBehavior), new FrameworkPropertyMetadata(false, OnIsActiveChanged));
public static bool GetIsActive(DependencyObject obj)
{
return (bool)obj.GetValue(IsActiveProperty);
}
public static void SetIsActive(DependencyObject obj, bool value)
{
obj.SetValue(IsActiveProperty, value);
}
private static void OnIsActiveChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is FrameworkElement control))
{
return;
}
if((bool)e.NewValue)
{
if (GetParentElement(control) != null)
{
StartAnimation(control);
}
else
{
// If IsActive is set to true and the other properties haven't
// been updated yet, defer the animation until render time.
control.Dispatcher?.BeginInvoke((Action) (() => { StartAnimation(control); }), DispatcherPriority.Render);
}
}
else
{
StopAnimation();
}
}
#endregion
#region ParentElement (DependencyProperty)
public static readonly DependencyProperty ParentElementProperty = DependencyProperty.RegisterAttached("ParentElement", typeof(FrameworkElement), typeof(CountDownBarAnimationBehavior), new FrameworkPropertyMetadata(null, OnParentElementChanged));
public static FrameworkElement GetParentElement(DependencyObject obj)
{
return (FrameworkElement)obj.GetValue(ParentElementProperty);
}
public static void SetParentElement(DependencyObject obj, FrameworkElement value)
{
obj.SetValue(ParentElementProperty, value);
}
private static void OnParentElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(!(d is FrameworkElement fe))
{
return;
}
// You can wire up events here if you want to react to size changes, etc.
}
private static void OnParentElementSizeChanged(object sender, SizeChangedEventArgs e)
{
if (!(sender is FrameworkElement fe))
{
return;
}
if (GetIsActive(fe))
{
StopAnimation();
StartAnimation(fe);
}
}
#endregion
#region TargetElement (DependencyProperty)
public static readonly DependencyProperty TargetElementProperty = DependencyProperty.RegisterAttached("TargetElement", typeof(FrameworkElement), typeof(CountDownBarAnimationBehavior), new FrameworkPropertyMetadata(null));
public static FrameworkElement GetTargetElement(DependencyObject obj)
{
return (FrameworkElement)obj.GetValue(TargetElementProperty);
}
public static void SetTargetElement(DependencyObject obj, FrameworkElement value)
{
obj.SetValue(TargetElementProperty, value);
}
#endregion
private static void StartAnimation(DependencyObject d)
{
var parent = GetParentElement(d);
var target = GetTargetElement(d);
if (parent == null || target == null)
{
return;
}
sb = new Storyboard();
var da = new DoubleAnimation();
Storyboard.SetTarget(da, target);
Storyboard.SetTargetProperty(da, new PropertyPath("Width"));
da.AutoReverse = false;
da.Duration = new Duration(new TimeSpan(0, 1, 0));
da.From = parent.ActualWidth;
da.To = 0d;
sb.Children.Add(da);
sb.Begin();
}
private static void StopAnimation()
{
sb?.Stop();
}
}
}
In my control having content presenter which inside the canvas panel. I have arranged((Align as center, left and right in screen point) the content in canvas based on content actual size. Initial content size loaded properly and when dynamically change content, it actual size always return 0. Due to this, can't align content in screen position. Can you please advise me how to get content size in dynamic case like below scenario
<Page.Resources>
<!--control style-->
<Style TargetType="local:CustomLabel">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomLabel">
<Grid>
<Canvas>
<Ellipse Height="10" Width="10" Canvas.Left="300" Canvas.Top="300" Fill="Red" />
</Canvas>
<Canvas>
<ContentPresenter Margin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=LabelMargin}" Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" />
</Canvas>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
<Grid>
<local:CustomLabel x:Name="label">
<local:CustomLabel.Content>
<Grid>
<TextBlock Text="Initial Label" />
</Grid>
</local:CustomLabel.Content>
</local:CustomLabel>
<Button Content="Change text" VerticalAlignment="Bottom" Height="75" Click="Button_Click" />
</Grid>
public MainPage()
{
this.InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var grid = new Grid();
grid.Children.Add(new TextBlock() { Text = "Change" });
this.label.Content = grid;
}
// control implementation
public class CustomLabel : Control
{
bool loadTime = false;
public CustomLabel()
{
this.SizeChanged += CustomLabel_SizeChanged;
}
private void CustomLabel_SizeChanged(object sender, SizeChangedEventArgs e)
{
loadTime = true;
CalculateContentPosition();
}
private void CalculateContentPosition()
{
if (loadTime)
{
var content = Content as FrameworkElement;
if (content != null)
{
var left = content.ActualWidth / 2;
var top = content.ActualHeight / 2;
this.LabelMargin = new Thickness(300 - left, 300 - top, 0, 0);
}
}
}
public object Content
{
get { return (object)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
// Using a DependencyProperty as the backing store for Content. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(object), typeof(CustomLabel), new PropertyMetadata(null,OnContentChanged));
private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as CustomLabel).CalculateContentPosition();
}
public Thickness LabelMargin
{
get { return (Thickness)GetValue(LabelMarginProperty); }
set { SetValue(LabelMarginProperty, value); }
}
// Using a DependencyProperty as the backing store for LabelMargin. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LabelMarginProperty =
DependencyProperty.Register("LabelMargin", typeof(Thickness), typeof(CustomLabel), new PropertyMetadata(new Thickness(1)));
}
The issue was because you access its size too early, it even has not been rendered on XAML page. You might ask why the Grid has size on XAML page at first time. It's because the parent panel has done this operation(similar Measure(size)) when it layouts its child elements.
So, to solve your question, you could register the LayoutUpdated event of your 'CustomLabel'. Then, in its event handler, you could call CalculateContentPosition() method, instead of calling in OnContentChanged method.
public CustomLabel()
{
this.SizeChanged += CustomLabel_SizeChanged;
this.LayoutUpdated += CustomLabel_LayoutUpdated;
}
private void CustomLabel_LayoutUpdated(object sender, object e)
{
CalculateContentPosition();
}
I am creating a CustomControl which contain InkCanvas. Now the problem is How do I link InkToolbar(which is outside the CustomControl) to an InkCanvas(which is inside the CustomControl)?
Solution Tried:
I tried to get the InkCanvas outside the CustomControl using below code but It is not working.
Here is my code(With the solution I tried which is not working):
//In CustomControl Code Behind
InkCanvas PATH_INK_CANVAS;
protected override void OnApplyTemplate()
{
PATH_INK_CANVAS = GetTemplateChild<InkCanvas>("PATH_INK_CANVAS");
}
T GetTemplateChild<T>(string elementName) where T : DependencyObject
{
var element = GetTemplateChild(elementName) as T;
if (element == null)
throw new NullReferenceException(elementName);
return element;
}
public InkCanvas InkCanvas
{
get { return PATH_INK_CANVAS; }
}
public static readonly DependencyProperty InkCanvasProperty =
DependencyProperty.Register("InkCanvas", typeof(InkCanvas), typeof(RichInkTextBox), new PropertyMetadata(0));
//In CustomControl XAML
<Style>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Name="MainGrid" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
<InkCanvas Name="PATH_INK_CANVAS" Canvas.ZIndex="-1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
//In Page
<local:CustomControl x:Name="MyCustomControl"/>
<InkToolbar Grid.Row="0" TargetInkCanvas="{x:Bind MyCustomControl.InkCanvas}"/>
I don't think that's the right syntax to define a read-only dependency property. Try something like the following instead -
public InkCanvas InkCanvas
{
get => (InkCanvas)GetValue(InkCanvasProperty);
private set => SetValue(InkCanvasProperty, value);
}
public static readonly DependencyProperty InkCanvasProperty = DependencyProperty.Register(
"InkCanvas", typeof(InkCanvas), typeof(InkCanvasWrapper), new PropertyMetadata(null));
Also, make sure you set the Mode of the x:Bind to OneWay as the default value of the InkCanvas dependency property is null (you are setting the default value to 0 which is wrong).
<InkToolbar Grid.Row="0" TargetInkCanvas="{x:Bind MyCustomControl.InkCanvas, Mode=OneWay}" />
I need to show an image (fixed-size) in a WPF application.
It should be able to mark the image with pins as shown in above
image.
It should be able to add description for each pins and, when hovering
on the pin the description should be shown.
Finally I need to save all the information in SQL database to display
the pins again.
Is that possible to achieve this by creating a custom control?
Please suggest me your ideas for implementing this solution.
Providing examples will be highly appreciated.
To answer your question: Yes it's possible.
I would highly recommend the MVVM architectural pattern when working with WPF. What you need is:
A canvas control in order to use absolute positioning
An image control that will display the background image
A custom pin control that will display the image of the pins. This control could also contain a DataTemplate that will be used to generate the description control.
A custom control that will display information about the pin (Will be used in the popup)
An adorner that will render the pin info popup in an adorner layer. Place the adorner decorator in the same position as the canvas.
The information that you need to store about a pin:
Its Canvas.Top and Canvas.Left values
Properties that affect its visual characteristics (e.g. image, color etc)
The information displayed in its popup (e.g. description, image)
You can then read all the entries from the database and create a pin view model for each entry and bind the view models to an items control in the canvas. Don't forget to bind properties of the pin control to the respective values of its view model (e.g. Canvas.Left, Canvas.Top, Description etc).
As for the popup, once you created your adorner class, add an instance of it to the adorner layer of your canvas when you need to show the popup and remove it when you need to close the popup.
An example of the style of the map control can be seen below (Assumes view model of map control contains an observable collection of pins):
<Style TargetType="{x:Type local:Map}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Map}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<AdornerDecorator></AdornerDecorator>
<ItemsControl ItemsSource="{Binding Path=Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="White">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:Pin></local:Pin>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here's an example of an adorner control that simply renders a given FrameworkElement:
public class ControlAdorner : Adorner {
FrameworkElement _control;
public FrameworkElement Control {
get {
return (_control);
}
set {
_control = value;
}
}
public ControlAdorner(UIElement Element, FrameworkElement Control)
: base(Element) {
this.Control = Control;
this.AddVisualChild(this.Control);
this.IsHitTestVisible = false;
}
protected override Visual GetVisualChild(int index) {
if (index != 0) throw new ArgumentOutOfRangeException();
return _control;
}
protected override int VisualChildrenCount {
get {
return 1;
}
}
public void UpdatePosition(Point point) {
VisualOffset = new Vector(point.X, point.Y);
this.InvalidateVisual();
}
protected override Size MeasureOverride(Size constraint) {
Control.Measure(constraint);
return Control.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize) {
Control.Arrange(new Rect(new Point(VisualOffset.X, VisualOffset.Y - 20), finalSize));
return new Size(Control.ActualWidth, Control.ActualHeight);
}
}
And here's how to make the Pin control display the adorner when the mouse is hovering:
public class Pin : Control {
public DataTemplate DescriptionItemTemplate {
get { return (DataTemplate)GetValue(DescriptionItemTemplateProperty); }
set { SetValue(DescriptionItemTemplateProperty, value); }
}
public static readonly DependencyProperty DescriptionItemTemplateProperty =
DependencyProperty.Register("DescriptionItemTemplate", typeof(DataTemplate), typeof(Pin), new PropertyMetadata(null));
ControlAdorner _adorner;
AdornerLayer _adornerLayer;
static Pin() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(Pin), new FrameworkPropertyMetadata(typeof(Pin)));
}
public Pin() {
this.MouseEnter += Pin_MouseEnter;
this.MouseLeave += Pin_MouseLeave;
}
private void Pin_MouseEnter(object sender, MouseEventArgs e) {
_adornerLayer = AdornerLayer.GetAdornerLayer(this);
FrameworkElement element = DescriptionItemTemplate.LoadContent() as FrameworkElement;
if (element == null) { return; }
element.DataContext = this.DataContext;
_adorner = new ControlAdorner(this, element);
_adornerLayer.Add(_adorner);
}
private void Pin_MouseLeave(object sender, MouseEventArgs e) {
_adornerLayer.Remove(_adorner);
_adorner = null;
}
}
I'm trying to create a button user control which can display Image from xaml by adding property (ShowImage="ImagePath").
I've bound the user control Image source to the button's Content in the xaml file:
<UserControl x:Class="testUserControl.UserControls.TestDependencyShowImage"
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"
mc:Ignorable="d"
Height="auto" Width="auto">
<Grid>
<Button MinHeight="30" MinWidth="50">
<Button.Template>
<ControlTemplate TargetType="Button">
<Image Source="{TemplateBinding Content}"/>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</UserControl>
And I've created a Dependency property which create BitmapImage and set it to the content(in the meantime hard coded path just to see if it can be done).
cs:
namespace testUserControl.UserControls
{
/// <summary>
/// Interaction logic for TestDependencyShowImage.xaml
/// </summary>
public partial class TestDependencyShowImage : UserControl
{
private static BitmapImage s_oImage = null;
private static string s_strSourceImage = null;
public static readonly DependencyProperty ShowImageDP = DependencyProperty.Register("ShowImage", typeof(string), typeof(TestDependencyShowImage), new PropertyMetadata(null, new PropertyChangedCallback(SetImage)));
public string ShowImage
{
get
{
return (string)GetValue(ShowImageDP);
}
set
{
SetValue(ShowImageDP, value);
this.Content = s_oImage;
//OnTargetPowerChanged(this, new DependencyPropertyChangedEventArgs(TargetPowerProperty, value, value)); // Old value irrelevant.
}
}
private static void SetImage(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
TestDependencyShowImage muc = (TestDependencyShowImage)obj;
s_strSourceImage = (string)args.NewValue;
if (s_strSourceImage != null)
{
s_oImage = new BitmapImage(new Uri(#"C:\Users\AmitL\Desktop\james-brown-010.jpg", UriKind.Absolute));
//BitmapImage l_oImage = new BitmapImage(new Uri(value));
}
}
public TestDependencyShowImage()
{
InitializeComponent();
this.Content = s_oImage;
}
}
}
You really don't need any of that code. Just let the Framework convert the string file path into the image for you. Try this code inside your UserControl:
<Image Source="{Binding ShowImage, RelativeSource={RelativeSource
AncestorType={x:Type YourXamlNamespacePrefix:TestDependencyShowImage}}}" />