How to bind GradientStopCollection with some GradientStops in ViewModel
<Rectangle Width="30" Height="200">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStopCollection ????? />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
The GradientBrush (baseclass of LinearGradientBrush) is defined with [ContentProperty("GradientStops")], which means, that the GradientStopCollection can be either set directly as content or explicitely as GradientStops property.
So you can define a GradientStopCollection in resources or code behind and then bind it to your brush.
A little example XAML:
<Grid x:Name="grid1">
<Rectangle Width="30" Height="200" Margin="20">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1"
GradientStops="{Binding}">
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
Code Behind:
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
GradientStopCollection c = new GradientStopCollection();
c.Add(new GradientStop(Colors.Red, 0.0));
c.Add(new GradientStop(Colors.Green, 0.5));
c.Add(new GradientStop(Colors.Yellow, 1.0));
grid1.DataContext = c;
}
I don't think you can manually add/remove individual collection items without code behind, but as said, you should be able to define / replace the whole collection in XAML as well.
Edit
So let's suppose you want to decouple your viewmodel a bit more:
public class GradientTransferObject
{
public Color Color { get; set; }
public double Offset { get; set; }
}
// ....
public ObservableCollection<GradientTransferObject> Gradients { get; set; }
Now there is a need to translate the source items into an GradientStopCollection. This can be done with something similar to a CollectionViewSource - an object that can be hosted as resource, takes an items source and provides an item collection.
public class GradientProvider : Freezable
{
// the resulting items collection
public GradientStopCollection GradientItems
{
get { return (GradientStopCollection)GetValue(GradientItemsProperty); }
private set { SetValue(GradientItemsPropertyKey, value); }
}
private static readonly DependencyPropertyKey GradientItemsPropertyKey =
DependencyProperty.RegisterReadOnly("GradientItems", typeof(GradientStopCollection), typeof(GradientProvider), new PropertyMetadata(null));
public static readonly DependencyProperty GradientItemsProperty = GradientItemsPropertyKey.DependencyProperty;
// the items source from viewmodel data
public IEnumerable<GradientTransferObject> GradientItemsSource
{
get { return (IEnumerable<GradientTransferObject>)GetValue(GradientItemsSourceProperty); }
set { SetValue(GradientItemsSourceProperty, value); }
}
public static readonly DependencyProperty GradientItemsSourceProperty =
DependencyProperty.Register("GradientItemsSource", typeof(IEnumerable<GradientTransferObject>), typeof(GradientProvider),
new PropertyMetadata(null, new PropertyChangedCallback(OnItemsSourceChanged)));
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var self = d as GradientProvider;
if (e.OldValue == e.NewValue)
{
return;
}
if (e.OldValue is ObservableCollection<GradientTransferObject>)
{
var c = e.OldValue as ObservableCollection<GradientTransferObject>;
c.CollectionChanged -= self.CollectionChanged;
}
if (e.NewValue is ObservableCollection<GradientTransferObject>)
{
var c = e.NewValue as ObservableCollection<GradientTransferObject>;
c.CollectionChanged += self.CollectionChanged;
}
self.UpdateItems();
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdateItems();
}
private void UpdateItems()
{
GradientItems = new GradientStopCollection(GradientItemsSource.Select(x => new GradientStop(x.Color, x.Offset)));
}
protected override Freezable CreateInstanceCore()
{
return new GradientProvider();
}
}
The updated xaml example:
<Grid x:Name="grid1">
<Grid.Resources>
<local:GradientProvider x:Key="gradientSource" GradientItemsSource="{Binding}"/>
</Grid.Resources>
<Rectangle Width="30" Height="200" Margin="20">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1"
GradientStops="{Binding GradientItems,Source={StaticResource gradientSource}}">
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Button VerticalAlignment="Top" HorizontalAlignment="Left" Margin="5" Content="AddGradient" Click="Button_Click"/>
</Grid>
The updated code, dynamically changing the gradient collection on button click:
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Gradients = new ObservableCollection<GradientTransferObject>()
{
new GradientTransferObject{ Color = Colors.Red, Offset = 0.0},
new GradientTransferObject{ Color = Colors.Yellow, Offset = 1.0},
};
grid1.DataContext = Gradients;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Gradients.Add(new GradientTransferObject { Color = Colors.Green, Offset = 0.5 });
}
So as you see, you are free to take it easy with direct usage of GradientStopCollection in the viewmodel or go for full abstraction where your viewmodel source and your view work on completely different data types.
Related
This is a WPF question.
I am trying to track / drag the vertices of a PathGeometry on a Canvas using Thumb Controls to drag the vertices.
It seems that the PathGeometry is scaled differently than the Thumb positions relative to the Canvas.
How can I compute the scaling ratio? Once I have that, I can use a ScaleTransform to correct it.
Thanks in advance.
Here is my XAML. I have a scale value of 3 hard coded, but it doesn't work if my window size changes:
MyControl.XAML
<UserControl x:Class="WPF_Discovery_Client.ColorOpacityControl"
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:local="clr-namespace:WPF_Discovery_Client"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<Style TargetType="{x:Type Thumb}" x:Key="RoundThumb">
<Style.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="10" />
<Setter Property="BorderThickness" Value="1" />
</Style>
</Style.Resources>
</Style>
</UserControl.Resources>
<Grid>
<Canvas Background="aqua" x:Name="MyCanvas" SizeChanged="MyCanvas_SizeChanged" Loaded="MyCanvas_Loaded" >
<Path Stroke="Black" StrokeThickness="1" Height="450" Stretch="Fill" Width="800" >
<Path.Fill>
<LinearGradientBrush ColorInterpolationMode="ScRgbLinearInterpolation" StartPoint="0,0" EndPoint="1,0">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="0.17" Color="Orange"/>
<GradientStop Offset="0.34" Color="Yellow"/>
<GradientStop Offset="0.51" Color="Green"/>
<GradientStop Offset="0.68" Color="Blue"/>
<GradientStop Offset="0.85" Color="Indigo"/>
<GradientStop Offset="1.0" Color="Violet"/>
</LinearGradientBrush>
</Path.Fill>
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="MyPath" IsClosed="True" StartPoint="{Binding BottomLeftCorner, Mode=TwoWay}">
<PathFigure.Segments>
<PathSegmentCollection>
<LineSegment Point="{Binding LeftVertex, Mode=TwoWay}"/>
<LineSegment Point="{Binding MiddleVertex, Mode=TwoWay}" />
<LineSegment Point="{Binding RightVertex, Mode=TwoWay}" />
<LineSegment Point="{Binding BottomRightCorner, Mode=TwoWay}" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
<Path.RenderTransform>
<ScaleTransform ScaleX="2.0" ScaleY="2.0"/>
</Path.RenderTransform>
</Path>
<Thumb Name="LeftThumb" Style="{DynamicResource RoundThumb}" Background="White"
Width="20" Height="20" DragDelta="LeftThumb_DragDelta"
DragStarted="LeftThumb_DragStarted" DragCompleted="LeftThumb_DragCompleted"/>
<Thumb Name="MiddleThumb" Style="{DynamicResource RoundThumb}" Background="White"
Width="20" Height="20" DragDelta="MiddleThumb_DragDelta"
DragStarted="MiddleThumb_DragStarted" DragCompleted="MiddleThumb_DragCompleted"/>
<Thumb Name="RightThumb" Style="{DynamicResource RoundThumb}" Background="White"
Width="20" Height="20" DragDelta="RightThumb_DragDelta"
DragStarted="RightThumb_DragStarted" DragCompleted="RightThumb_DragCompleted"/>
</Canvas>
</Grid>
</UserControl>
And here is the code behind for the control:
MyControl.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Globalization;
using System.Windows.Controls.Primitives;
using System.ComponentModel;
namespace WPF_Discovery_Client
{
/// <summary>
/// Interaction logic for ColorOpacityControl.xaml
/// </summary>
public partial class ColorOpacityControl : UserControl, INotifyPropertyChanged
{
const int ThumbRadius = 10;
// Bottom corners
public Point BottomRightCorner
{
get { return (Point)GetValue(BottomRightCornerProperty); }
set { SetValue(BottomRightCornerProperty, value); }
}
// Using a DependencyProperty as the backing store for BottomRightCorner. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BottomRightCornerProperty =
DependencyProperty.Register("BottomRightCorner", typeof(Point), typeof(ColorOpacityControl), new PropertyMetadata(new Point(0, 100)));
public Point BottomLeftCorner
{
get { return (Point)GetValue(BottomLeftCornerProperty); }
set { SetValue(BottomLeftCornerProperty, value); }
}
// Using a DependencyProperty as the backing store for BottomLeftCorner. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BottomLeftCornerProperty =
DependencyProperty.Register("BottomLeftCorner", typeof(Point), typeof(ColorOpacityControl), new PropertyMetadata(new Point(0, 200)));
// Thumb center locations
public Point LeftVertex
{
get { return (Point)GetValue(LeftVertexProperty); }
set { SetValue(LeftVertexProperty, value); }
}
// Using a DependencyProperty as the backing store for LeftVertex. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LeftVertexProperty =
DependencyProperty.Register("LeftVertex", typeof(Point), typeof(ColorOpacityControl), new PropertyMetadata(new Point(0,266)));
public Point MiddleVertex
{
get { return (Point)GetValue(MiddleVertexProperty); }
set { SetValue(MiddleVertexProperty, value); }
}
// Using a DependencyProperty as the backing store for MiddleVertex. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MiddleVertexProperty =
DependencyProperty.Register("MiddleVertex", typeof(Point), typeof(ColorOpacityControl), new PropertyMetadata(new Point(100, 100)));
public Point RightVertex
{
get { return (Point)GetValue(RightVertexProperty); }
set { SetValue(RightVertexProperty, value); }
}
// Using a DependencyProperty as the backing store for RightVertex. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RightVertexProperty =
DependencyProperty.Register("RightVertex", typeof(Point), typeof(ColorOpacityControl), new PropertyMetadata(new Point(100, 50)));
public ColorOpacityControl()
{
InitializeComponent();
DataContext = this;
}
private void LeftThumb_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
LeftThumb.Background = Brushes.Red;
}
private void LeftThumb_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
{
LeftThumb.Background = Brushes.White;
}
private void LeftThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
//Move the Thumb to the mouse position during the drag operation
var yadjust = MyCanvas.ActualHeight + e.VerticalChange;
var xadjust = MyCanvas.ActualWidth + e.HorizontalChange;
if ((xadjust >= 0) && (yadjust >= 0))
{
// Compute new thumb location
double X = Canvas.GetLeft(LeftThumb) + e.HorizontalChange;
double Y = Canvas.GetTop(LeftThumb) + e.VerticalChange;
// Move thumb
Canvas.SetLeft(LeftThumb, X);
Canvas.SetTop(LeftThumb, Y);
// Compute center of thumb as vertex location
LeftVertex = new Point(X + LeftThumb.Width, Y + LeftThumb.Height);
NotifyPropertyChanged("LeftVertex");
}
}
private void MiddleThumb_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
MiddleThumb.Background = Brushes.Green;
}
private void MiddleThumb_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
{
MiddleThumb.Background = Brushes.White;
}
private void MiddleThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
//Move the Thumb to the mouse position during the drag operation
var yadjust = MyCanvas.ActualHeight + e.VerticalChange;
var xadjust = MyCanvas.ActualWidth + e.HorizontalChange;
if ((xadjust >= 0) && (yadjust >= 0))
{
// Compute new thumb location
double X = Canvas.GetLeft(MiddleThumb) + e.HorizontalChange;
double Y = Canvas.GetTop(MiddleThumb) + e.VerticalChange;
// Move thumb
Canvas.SetLeft(MiddleThumb, X);
Canvas.SetTop(MiddleThumb, Y);
// Compute center of thumb as vertex location
MiddleVertex = new Point(X + MiddleThumb.Width, Y + MiddleThumb.Height);
NotifyPropertyChanged("MiddleVertex");
}
}
private void RightThumb_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
RightThumb.Background = Brushes.Yellow;
}
private void RightThumb_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
{
RightThumb.Background = Brushes.White;
}
private void RightThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
//Move the Thumb to the mouse position during the drag operation
var yadjust = MyCanvas.ActualHeight + e.VerticalChange;
var xadjust = MyCanvas.ActualWidth + e.HorizontalChange;
if ((xadjust >= 0) && (yadjust >= 0))
{
// Compute new thumb location
double X = Canvas.GetLeft(RightThumb) + e.HorizontalChange;
double Y = Canvas.GetTop(RightThumb) + e.VerticalChange;
// Move thumb
Canvas.SetLeft(RightThumb, X);
Canvas.SetTop(RightThumb, Y);
// Compute center of thumb as vertex location
RightVertex = new Point(X + ThumbRadius, Y + ThumbRadius);
NotifyPropertyChanged("RightVertex");
}
}
private void MyCanvas_SizeChanged(object sender, SizeChangedEventArgs e)
{
// Adjust bottom left corners
BottomLeftCorner = new Point(0, MyCanvas.ActualHeight);
NotifyPropertyChanged("BottomLeftCorner");
// Adjust botton right corner
BottomRightCorner = new Point(MyCanvas.ActualWidth, MyCanvas.ActualHeight);
NotifyPropertyChanged("BottomRightCorner");
}
private void InitializeVertices()
{
// Initialize bottom left corner
BottomLeftCorner = new Point(ThumbRadius, MyCanvas.ActualHeight - ThumbRadius);
NotifyPropertyChanged("BottomLeftCorner");
// Initialize bottom right corner
BottomRightCorner = new Point(MyCanvas.ActualWidth - ThumbRadius, MyCanvas.ActualHeight - ThumbRadius);
NotifyPropertyChanged("BottomRightCorner");
// Initialize right vertex
RightVertex = new Point(MyCanvas.ActualWidth - ThumbRadius, ThumbRadius);
NotifyPropertyChanged("RightVertex");
// Initialize left vertex
LeftVertex = BottomLeftCorner;
NotifyPropertyChanged("LeftVertex");
// Initialize middle vertex
MiddleVertex = new Point(MyCanvas.ActualWidth * 0.5, MyCanvas.ActualHeight * 0.5);
NotifyPropertyChanged("MiddleVertex");
// Initialize Left Thumb
Canvas.SetLeft(LeftThumb, LeftVertex.X - ThumbRadius);
Canvas.SetTop(LeftThumb, LeftVertex.Y - ThumbRadius);
// Initialize Right Thumb
Canvas.SetLeft(RightThumb, RightVertex.X - ThumbRadius);
Canvas.SetTop(RightThumb, RightVertex.Y - ThumbRadius);
// Initialize Middle Thumb
Canvas.SetLeft(MiddleThumb, MiddleVertex.X - ThumbRadius);
Canvas.SetTop(MiddleThumb, MiddleVertex.Y - ThumbRadius);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
private void MyCanvas_Loaded(object sender, RoutedEventArgs e)
{
InitializeVertices();
}
}
}
As you can see, I am setting both the vertices and the thumbs to the same position relative to the canvas. However, if you run the code, you can see that the initial triangle with the gradient fill is much smaller than it needs to be. I want the 3 thumbs to coincide with the 2 vertices and the midpoint.
Moreover, I notice that I have a Height and Width specified for the Path, which I am not sure if I need. If I make it larger to match the size of the canvas, the triangle will grow. Should I set Height and with to *?
I am new to WPF graphics, so any help would be appreciated.
Well.. after banging my head on the wall for a couple days, I finally figured out how to fix this issue. Hopefully this will save someone a lot of time and hassle down the road.
The problem was that I had the Path width/height set to the size of the parent window, and the Stretch property set to Fill.
Once I made the following change, the scaling came out correct.
<Canvas Background="Black" x:Name="MyCanvas" SizeChanged="MyCanvas_SizeChanged" Loaded="MyCanvas_Loaded">
<Path x:Name="MyPath" Stroke="Black" StrokeThickness="1" Height="{Binding MyCanvas.ActualHeight}" Width="{Binding MyCanvas.ActualWidth}" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top">
I have this Ellipse with a RadialGradientBrush, where the colors are bind to the colors Light and Dark:
<Grid x:Name="gridEllipse">
<Ellipse x:Name="ellipseMPCenter">
<Ellipse.Fill>
<RadialGradientBrush GradientOrigin="50,50" Center="50,50" Radius="1">
<RadialGradientBrush.GradientStops>
<GradientStop Color="{Binding Light}" Offset="0"/>
<GradientStop Color="{Binding Dark}" Offset="1"/>
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
</Grid>
This is my code:
public class UserControlLED : UserControl, INotifyPropertyChanged
{
private readonly Color colorGreenLight = Color.FromRgb(61, 214, 0);
private readonly Color colorGreenDark = Color.FromRgb(10, 92, 1);
private readonly Color colorRedLight = Color.FromRgb(235, 0, 0);
private readonly Color colorRedDark = Color.FromRgb(130, 0, 0);
private Color light;
public Color Light
{
get
{
return light;
}
set
{
light = value;
OnPropertyChanged(nameof(Light));
}
}
private Color dark;
public Color Dark
{
get
{
return dark;
}
set
{
dark = value;
OnPropertyChanged(nameof(Dark));
}
}
public UserControlLED()
{
InitializeComponent();
Light = colorGreenLight;
Dark = colorGreenDark;
gridEllipse.DataContext = this;
}
private async void BtStartClicked(object sender, RoutedEventArgs args)
{
Light = colorRedLight;
Dark = colorRedDark;
}
public event EventHandler<PropertyChangedEventArgs> PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
With this code, the Ellipse is colored green, even if I press the button.
When I set the DataContext in the button click event again, nothing happens. Or do I need to put this in the OnPropertyChanged method? But this still does not work.
Where and when do I need to set the DataContext?
Fix the Center property of the RadialGradientBrush and use RelativeSource Bindings:
<Ellipse.Fill>
<RadialGradientBrush Center="0.5,0.5">
<RadialGradientBrush.GradientStops>
<GradientStop
Color="{Binding Light,
RelativeSource={RelativeSource AncestorType=UserControl}}"
Offset="0"/>
<GradientStop
Color="{Binding Dark,
RelativeSource={RelativeSource AncestorType=UserControl}}"
Offset="1"/>
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</Ellipse.Fill>
Do not implement INotifyPropertyChanged, but declare the properties as dependency properties:
public static readonly DependencyProperty LightProperty =
DependencyProperty.Register(
nameof(Light), typeof(Color), typeof(UserControlLED),
new PropertyMetadata(Colors.White));
public static readonly DependencyProperty DarkProperty =
DependencyProperty.Register(
nameof(Dark), typeof(Color), typeof(UserControlLED),
new PropertyMetadata(Colors.Black));
public Color Light
{
get { return (Color)GetValue(LightProperty); }
set { SetValue(LightProperty, value); }
}
public Color Dark
{
get { return (Color)GetValue(DarkProperty); }
set { SetValue(DarkProperty, value); }
}
Do not explicitly set the DataContext, because that would break the standard binding mechanism when you bind the control's properties like
<local:UserControlLED Light="{Binding SomeColor}" .../>
2nd Edit
so removing the Panel.ZIndex properties from the control template resolved this issue for me, giving me 1 drop event.
including them triggers two drop events.
can any one answer me why though?
id love to know why z index ?
Original Question :
I am trying to add a custom object (state) to a canvas called MainCanvas on the MainWindow.
I am trying to drag a state object from a wrap panel and drop it onto the canvas.
the code works but there are two items being added.
I know there are two because I can move the two item around the canvas.
I have searched existing answers and added e.Handled=true, but still adds two items
I tried using Drop event and PreviewDrop Event on MainCanvas, no difference.
Cam someone help as to how I can make it so that only 1 item gets added?
the maincanvas exists at design time
a new state is created at runtime at the drop event.
Here is the OnMouseMove handler for the state
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed)
{
var parent = VisualTreeHelper.GetParent(this);
if (parent as WrapPanel != null)
{
DataObject dragData = new DataObject();
dragData.SetData(DataFormats.StringFormat, this.ItemType);
DragDrop.DoDragDrop(this, dragData, DragDropEffects.Copy);
}
}
e.Handled = true;
}
Within the Code Behind I have set the following events for the canvas:
private void MainCanvas_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.Text))
e.Effects = DragDropEffects.Copy;
else
e.Effects = DragDropEffects.None;
e.Handled = true;
}
private void MainCanvas_Drop(object sender, DragEventArgs e)
{
var itemType = e.Data.GetData(typeof(string));
switch (itemType)
{
case "state":
var pos = e.GetPosition(this.MainCanvas);
State item = new State();
item.Template = (ControlTemplate)FindResource("StateViewModelControlTemplate");
this.MainCanvas.Children.Add(item);
Canvas.SetLeft(item, pos.X);
Canvas.SetTop(item, pos.Y);
e.Handled = true;
break;
default:
break;
}
e.Handled = true;
}
Finally here is the xaml for the Main Canvas
<Canvas x:Name="MainCanvas" x:Name="MainCanvas"
DockPanel.Dock="Top"
Background="#666"
Height="600"
Margin="4"
AllowDrop="True"
DragEnter="MainCanvas_DragEnter"
Drop="MainCanvas_Drop"/>
Edit:
ok so after lupus' response i went back and reconstructed everything from scratch in a separate temp project
<ControlTemplate x:Key="StateViewModelControlTemplate" TargetType="{x:Type vm:State}">
<Grid Width="100" Height="60">
<!--
If I comment out the following Thumb
the drop event will only trigger once
If i leave it in then it triggers twice
Move Thumb is derived from thumb
-->
<local:MoveThumb Panel.ZIndex="99"
x:Name="StateViewModelMoveThumb"
DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"
Opacity="0"/>
<Border Panel.ZIndex="98"
Margin="4"
Padding="4"
BorderBrush="white"
BorderThickness="2"
CornerRadius="5">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF59C7D4" Offset="0.5"/>
<GradientStop Color="#FF075A64" Offset="0"/>
<GradientStop Color="#FF00626E" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{TemplateBinding StateName}"/>
</Border>
</Grid>
</ControlTemplate>
By the way what I trying to do is very loosely based on the following article: https://www.codeproject.com/Articles/22952/WPF-Diagram-Designer-Part-1
I tried your code, and I cannot reproduce your issue. Changes:
private void MainCanvas_Drop(object sender, DragEventArgs e)
{
var itemType = e.Data.GetData(typeof(string));
switch (itemType)
{
var pos = e.GetPosition(this.MainCanvas);
Border item = new Border()
{
Width = 10,
Height = 10,
Background = Brushes.Red
};
//item.Template = (ControlTemplate)FindResource("StateViewModelControlTemplate");
this.MainCanvas.Children.Add(item);
...
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed)
{
DataObject dragData = new DataObject();
dragData.SetData(DataFormats.StringFormat, "state");
DragDrop.DoDragDrop(this, dragData, DragDropEffects.Copy);
}
e.Handled = true;
}
and in xaml,
<Window ...
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Canvas x:Name="MainCanvas" Grid.Column="0"
DockPanel.Dock="Top"
Background="#666"
Height="600"
Margin="4"
AllowDrop="True"
DragEnter="MainCanvas_DragEnter"
Drop="MainCanvas_Drop"/>
<Grid Grid.Column="1" Background="Red"/>
</Grid>
Have not touch the other piece of code. Everytime I drag from the Grid to the Canvas, I get one Border item at the drop position.
So maybe the problem is not in the code you posted, but in the code left out or commented.
So I eventually got this working and here is how
Here is the front end xaml on the mainwindow
<cc:DiagramCanvas x:Name="MainCanvas"
DockPanel.Dock="Top"
Margin="0"
MinHeight="450"
AllowDrop="True" Background="White">
</cc:DiagramCanvas>
Here is the custom canvas object and the drag drop handler
public class DiagramCanvas : Canvas
{
public DiagramCanvas()
{
this.Drop += DoDrop;
this.DragEnter += MainCanvas_DragEnter;
}
readonly MainWindow mainWin = (MainWindow)Application.Current.MainWindow;
#region works dont touch
public void DoDrop(object sender, DragEventArgs e)
{
var mainWin = (MainWindow)App.Current.MainWindow;
DragDropHandler<StateVM>.Instance.Drop(sender, e);
}
private void MainCanvas_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.Text))
e.Effects = DragDropEffects.Copy;
else
e.Effects = DragDropEffects.None;
e.Handled = true;
}
#endregion
//other code here
}
Here is the drag drop handler singleton
public sealed class DragDropHandler<T> where T : Control
{
#region singleton
private static DragDropHandler<T> instance = null;
private static readonly object padlock = new object();
DragDropHandler()
{
}
public static DragDropHandler<T> Instance
{
get
{
lock (padlock)
{
if (instance == null)
{
instance = new DragDropHandler<T>();
}
return instance;
}
}
private set
{
instance = value;
}
}
#endregion
public static bool IsDragging { get; set; }
public static WrapPanel AllowedDragSource { get; set; }
readonly MainWindow mainWin = (MainWindow)Application.Current.MainWindow;
public static void CreateInstance(WrapPanel allowedSource)
{
if (DragDropHandler<T>.IsDragging == false)
{
instance = new DragDropHandler<T>();
DragDropHandler<T>.AllowedDragSource = allowedSource;
}
}
public void Drag(object sender, MouseEventArgs e)
{
if (sender as T == null
|| mainWin.Radio_EditStates.IsChecked == false
|| e.LeftButton != MouseButtonState.Pressed
|| IsDragging == true)
{
e.Handled = true;
return;
}
var item = (T)sender;
if (Control.ReferenceEquals(item.Parent, AllowedDragSource) == false)
{
e.Handled = true;
return;
}
IsDragging = true;
DragDrop.DoDragDrop(((StateVM)sender), new DataObject(((StateVM)sender)), DragDropEffects.Copy);
IsDragging = false;
e.Handled = true;
}
public void Drop(object sender, DragEventArgs e)
{
var mainWin = (MainWindow)App.Current.MainWindow;
if (IsDragging)
{
//TODO: Switch here to handle different shapes
var pos = e.GetPosition(mainWin.MainCanvas);
//
Canvas.SetLeft(item, pos.X.RoundDownTo10());
Canvas.SetTop(item, pos.Y.RoundDownTo10());
//update main win observalbe collections to inclue the item dropped
IsDragging = false;
e.Handled = true;
DestroyInstance();
}
}
private static void DestroyInstance()
{
DragDropHandler<T>.Instance = null;
}
}
Here is the on mouse move code for the item you are dragging
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed && Control.ReferenceEquals(this.Parent, mainWin.libraryContainer))
{
DragDropHandler<StateVM>.CreateInstance(mainWin.libraryContainer);
if (DragDropHandler<StateVM>.Instance != null)
{
DragDropHandler<StateVM>.Instance.Drag(this, e);
}
}
}
I have a ControlTemplate which is for the Button control, in the ControlTemplate I have Image control which is used to displayed in the button, Now I want to set the Image Source at runt time as I have to copy paste the ControlTemplate for each of the button to set new image for new button.
Thanks in advance.
Maybe this is what you are looking for:
http://www.hardcodet.net/2009/01/create-wpf-image-button-through-attached-properties
http://blogs.msdn.com/knom/archive/2007/10/31/wpf-control-development-3-ways-to-build-an-imagebutton.aspx
http://social.msdn.microsoft.com/Forums/en-US/vswpfdesigner/thread/8ba13699-7f7f-4ab6-8e3e-f7d787355d81
Hope this helps.
Regards,
Mihir Gokani
Generally speaking there are two ways you can set an image source at run time (the code samples below are in pseudo-code and would not compile):
1) In XAML using binding where the source of the binding will be some object's property containing the image source (this is the scenario Slugster was talking about):
This would be your object:
public class ViewModel
{
public string ImageURI {get;set;}
}
and will be presented by XAML where you have your button with image, and image source is set through binding:
<Image Source="{Binding Source=ViewModel; Path=ImageURI}"/>
2) By setting the image source from code-behind.
this will be your XAML where you have the button with image:
<Image x:Name="theImage"/>
and in the code-behind you set the source of that image:
theImage.Source = new BitmapImage(new Uri("yor image uri"));
You can access an item from inside the template by using the GetTemplateChild(string childName) method (with the name of your element as defined in the XAML), for example - If your image was defined like this:
<Image x:Name="MyImage" Stretch="Fill" />
then you would call this method like this:
Image myImage = GetTemplateChild("MyImage") as Image;
if (myImage != null)
{
myImage.Source = "/Images/MyPicture.jpg";
}
Note: You will not be able to use this method until AFTER OnApplyTemplate has been called for the control.
<Button x:Class="FunitureCtlLib.PressedImageButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="uc"><!--MinHeight="25" MinWidth="50"-->
<Button.Template>
<ControlTemplate>
<Grid>
<Image Name="imgDefault" Source="{Binding Path=DefaultImageSource,ElementName=uc}" Stretch="{Binding Path=ImageStretch,ElementName=uc}"></Image>
<ContentPresenter Content="{TemplateBinding Property=ContentControl.Content}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Button.IsPressed" Value="True">
<Setter Property="Image.Source" TargetName="imgDefault" Value="{Binding Path=PressedImageSource,ElementName=uc}"></Setter>
<Setter Property="UIElement.Effect">
<Setter.Value>
<DropShadowEffect BlurRadius="10" Color="Black" Direction="0" Opacity="0.6" RenderingBias="Performance" ShadowDepth="0" />
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="UIElement.Effect">
<Setter.Value>
<DropShadowEffect BlurRadius="10" Color="White" Direction="0" Opacity="0.6" RenderingBias="Performance" ShadowDepth="0" />
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
/// <summary>
/// ImageButton.xaml
/// </summary>
public partial class PressedImageButton : Button
{
#region dependency property
public static readonly DependencyProperty DefaultImageSourceProperty = DependencyProperty.Register("DefaultImageSource", typeof(ImageSource), typeof(PressedImageButton), new PropertyMetadata(null, new PropertyChangedCallback(DefaultImageSourceChangedCallback)));
public static readonly DependencyProperty PressedImageSourceProperty = DependencyProperty.Register("PressedImageSource", typeof(ImageSource), typeof(PressedImageButton), new PropertyMetadata(null, new PropertyChangedCallback(PressedImageSourceChangedCallback)));
public static readonly DependencyProperty ImageStretchProperty = DependencyProperty.Register("ImageStretch", typeof(Stretch), typeof(PressedImageButton), new PropertyMetadata(Stretch.None, new PropertyChangedCallback(ImageStretchChangedCallback)));
#endregion
#region callback
private static void DefaultImageSourceChangedCallback(object sender, DependencyPropertyChangedEventArgs e)
{
if (sender != null && sender is PressedImageButton)
{
PressedImageButton imgbtn = sender as PressedImageButton;
imgbtn.OnDefaultImageSourceChanged(e.OldValue, e.NewValue);
}
}
private static void PressedImageSourceChangedCallback(object sender, DependencyPropertyChangedEventArgs e)
{
if (sender != null && sender is PressedImageButton)
{
PressedImageButton imgbtn = sender as PressedImageButton;
imgbtn.OnPressedImageSourceChanged(e.OldValue, e.NewValue);
}
}
private static void ImageStretchChangedCallback(object sender, DependencyPropertyChangedEventArgs e)
{
if (sender != null && sender is PressedImageButton)
{
PressedImageButton imgbtn = sender as PressedImageButton;
imgbtn.OnImageStretchChanged(e.OldValue, e.NewValue);
}
}
#endregion
#region public property
/// <summary>
///
/// </summary>
public ImageSource DefaultImageSource
{
get
{
return this.GetValue(DefaultImageSourceProperty) as ImageSource;
}
set
{
this.SetValue(DefaultImageSourceProperty, value);
}
}
/// <summary>
///
/// </summary>
public ImageSource PressedImageSource
{
get
{
return this.GetValue(PressedImageSourceProperty) as ImageSource;
}
set
{
this.SetValue(PressedImageSourceProperty, value);
}
}
/// <summary>
///
/// </summary>
public Stretch ImageStretch
{
get
{
return (Stretch)this.GetValue(ImageStretchProperty);
}
set
{
this.SetValue(ImageStretchProperty, value);
}
}
#endregion
#region protected method
protected void OnDefaultImageSourceChanged(object oldValue, object newValue)
{
//viewmodel.DefaultImageSource = newValue as ImageSource;
this.DefaultImageSource = newValue as ImageSource;
}
protected void OnPressedImageSourceChanged(object oldValue, object newValue)
{
//viewmodel.PressedImageSource = newValue as ImageSource;
this.PressedImageSource = newValue as ImageSource;
}
protected void OnImageStretchChanged(object oldValue, object newValue)
{
//viewmodel.ImageStretch = (Stretch)newValue;
this.ImageStretch = (Stretch)newValue;
}
#endregion
#region construct
public PressedImageButton()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(PressedImageButton_Loaded);
}
#endregion
#region private event
void PressedImageButton_Loaded(object sender, RoutedEventArgs e)
{
}
#endregion
}
I want to create a Style for a WPF ListBox that includes a Button in the ControlTemplate that the user can click on and it clears the ListBox selection.
I dont want to use codebehind so that this Style can be applied to any ListBox.
I have tried using EventTriggers and Storyboards and it has proved problematic as it only works first time and stopping the Storyboard sets the previous selection back.
I know I could use a user control but I want to know if it is possible to achieve this using only a Style.
It is not possible to achieve this using XAML and only the classes provided by the .NET framework. However you can still produce a reusable solution by defining a new command (call it ClearSelectionCommand) and a new attached property (call it ClearSelectionOnCommand).
Then you can incorporate those elements into your style.
Example:
public class SelectorBehavior
{
public static RoutedCommand
ClearSelectionCommand =
new RoutedCommand(
"ClearSelectionCommand",
typeof(SelectorBehavior));
public static bool GetClearSelectionOnCommand(DependencyObject obj)
{
return (bool)obj.GetValue(ClearSelectionOnCommandProperty);
}
public static void SetClearSelectionOnCommand(
DependencyObject obj,
bool value)
{
obj.SetValue(ClearSelectionOnCommandProperty, value);
}
public static readonly DependencyProperty ClearSelectionOnCommandProperty =
DependencyProperty.RegisterAttached(
"ClearSelectionOnCommand",
typeof(bool),
typeof(SelectorBehavior),
new UIPropertyMetadata(false, OnClearSelectionOnCommandChanged));
public static void OnClearSelectionOnCommandChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
Selector selector = d as Selector;
if (selector == null) return;
bool nv = (bool)e.NewValue, ov = (bool)e.OldValue;
if (nv == ov) return;
if (nv)
{
selector.CommandBindings.Add(
new CommandBinding(
ClearSelectionCommand,
ClearSelectionCommand_Executed,
ClearSelectionCommand_CanExecute));
}
else
{
var cmd = selector
.CommandBindings
.Cast<CommandBinding>()
.SingleOrDefault(x =>
x.Command == ClearSelectionCommand);
if (cmd != null)
selector.CommandBindings.Remove(cmd);
}
}
public static void ClearSelectionCommand_Executed(
object sender,
ExecutedRoutedEventArgs e)
{
Selector selector = (Selector)sender;
selector.SelectedIndex = -1;
}
public static void ClearSelectionCommand_CanExecute(
object sender,
CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
}
Example usage - XAML:
<Window x:Class="ClearSelectionBehaviorLibrary.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ClearSelectionBehaviorLibrary"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style x:Key="MyStyle" TargetType="Selector">
<Setter
Property="local:SelectorBehavior.ClearSelectionOnCommand"
Value="True"/>
</Style>
</Window.Resources>
<Grid>
<DockPanel>
<Button
DockPanel.Dock="Bottom"
Content="Clear"
Command="{x:Static local:SelectorBehavior.ClearSelectionCommand}"
CommandTarget="{Binding ElementName=TheListBox}"/>
<ListBox
Name="TheListBox"
ItemsSource="{Binding MyData}"
Style="{StaticResource MyStyle}"/>
</DockPanel>
</Grid>
</Window>
Example usage - Code Behind:
public partial class Window1 : Window
{
public List<string> MyData { get; set; }
public Window1()
{
MyData = new List<string>
{
"aa","bb","cc","dd","ee"
};
InitializeComponent();
DataContext = this;
}
}