I have been trying to set up a two-way binding in wpf. There is a canvas that is populated with ContentControls, each one containing a filled rectangle. Through a thumb, each ContentControl can be made larger and therefore have a changed width.
These ContentControls have been generated by code and live within a class (CanvasElement), which is used for other calculations.
I'd like to set up a two way binding between the ContentControl Property Width and the public double variable Width within the CanvasElement class. When the Thumb is used to change the width of the contentControl, the Width of the CanvasElement is updated, but the other way it doesn't work.
Here is what I have so far:
public class CanvasElement
{
private double width;
public double height;
private Point location; // This is the upper left point of the rectangle
public Brush color;
public string UID;
public ContentControl canvasElement;
public CanvasElement(Point location, double width, double height, Brush color, string UID)
{
this.location = location;
this.width = width;
this.height = height;
this.color = color;
this.UID = UID;
canvasElement = new ContentControl() { Width = this.width, Height = this.height, Uid = UID };
Canvas.SetLeft(canvasElement, this.location.X);
Canvas.SetTop(canvasElement, this.location.Y);
canvasElement.Content = new Rectangle() {
IsHitTestVisible = false,
Fill = this.color,
Stroke =Brushes.LightGray,
StrokeThickness = 2,
Margin = new Thickness(0,5,0,5),
RadiusX = 10,
RadiusY = 10};
addBinding();
}
private void addBinding()
{
Binding widthBinding = new Binding();
widthBinding.Source = this;
widthBinding.Path = new PropertyPath("Width");
widthBinding.Mode = BindingMode.TwoWay;
widthBinding.NotifyOnSourceUpdated = true;
widthBinding.NotifyOnTargetUpdated = true;
//widthBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
BindingOperations.SetBinding(canvasElement, ContentControl.WidthProperty, widthBinding);
}
public double Width
{
get
{
return width;
}
set
{
if(width != value)
{
width = value;
OnPropertyChanged();
}
}
}
As well as:
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
I am thankful for every hint I can get!
Thanks for helping out!
As Clemens pointed out in his comment the ItemsControl is the right way to do this. As I have different UIElements that are added to the canvas I needed to add an ItemsControl.ItemTemplateSelector as well as an ItemsControlItemContainerStyleSelector.
XAML
<AdornerDecorator ClipToBounds="True">
<ItemsControl ItemsSource="{Binding CanvasElementList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="FloralWhite"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyleSelector>
<local:CustomStyleSelector>
<local:CustomStyleSelector.CanvasStyle_TL>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=X, Mode=TwoWay}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=Y, Mode=TwoWay}"/>
</Style>
</local:CustomStyleSelector.CanvasStyle_TL>
<local:CustomStyleSelector.CanvasStyle_TR>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Right" Value="{Binding Path=X, Mode=TwoWay}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=Y, Mode=TwoWay}"/>
</Style>
</local:CustomStyleSelector.CanvasStyle_TR>
<local:CustomStyleSelector.CanvasStyle_BL>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=X, Mode=TwoWay}"/>
<Setter Property="Canvas.Bottom" Value="{Binding Path=Y, Mode=TwoWay}"/>
</Style>
</local:CustomStyleSelector.CanvasStyle_BL>
<local:CustomStyleSelector.CanvasStyle_BR>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Right" Value="{Binding Path=X, Mode=TwoWay}"/>
<Setter Property="Canvas.Bottom" Value="{Binding Path=Y, Mode=TwoWay}"/>
</Style>
</local:CustomStyleSelector.CanvasStyle_BR>
<local:CustomStyleSelector.LineStyle>
<Style TargetType="ContentPresenter">
</Style>
</local:CustomStyleSelector.LineStyle>
</local:CustomStyleSelector>
</ItemsControl.ItemContainerStyleSelector>
<ItemsControl.ItemTemplateSelector>
<local:CustomTemplateSelectors>
<local:CustomTemplateSelectors.LabelTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Text}"
FontWeight="{Binding FontWeight}"
FontSize="{Binding FontSize}"/>
</DataTemplate>
</local:CustomTemplateSelectors.LabelTemplate>
<local:CustomTemplateSelectors.LineTemplate>
<DataTemplate>
<Line X1="{Binding X1}"
X2="{Binding X2}"
Y1="{Binding Y1}"
Y2="{Binding Y2}"
Stroke="{Binding Stroke}"
StrokeThickness="{Binding StrokeThickness}"
StrokeDashArray="{Binding StrokeDashArray}"/>
</DataTemplate>
</local:CustomTemplateSelectors.LineTemplate>
<local:CustomTemplateSelectors.CanvasElementTemplate>
<DataTemplate>
<ContentControl Width="{Binding Path=Width, Mode=TwoWay}" Height="{Binding Path=Height, Mode=TwoWay}"
Style="{StaticResource ResourceKey=DesignerItemStyle}"
MouseDoubleClick="ContentControl_MouseDoubleClick">
<Rectangle Fill="{Binding Color}"
Stroke="LightGray"
StrokeThickness="2"
Margin="0,5,0,5"
RadiusX="10"
RadiusY="10"
IsHitTestVisible="False"/>
</ContentControl>
</DataTemplate>
</local:CustomTemplateSelectors.CanvasElementTemplate>
</local:CustomTemplateSelectors>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
</AdornerDecorator>
Code
In the corresponding .cs file there are these ObservableCollections and the CompositeCollection. The later is the element that binds to the ItemsControl. To add new elements you have to add elements to the Observable Collections
CanvasElementList4Canvas = new ObservableCollection<CanvasElement>();
LineList4Canvas = new ObservableCollection<CustomLine>();
LabelList4Canvas = new ObservableCollection<LabelTextBlock>();
CanvasElementList = new CompositeCollection();
CanvasElementList.Add(new CollectionContainer() { Collection = CanvasElementList4Canvas });
CanvasElementList.Add(new CollectionContainer() { Collection = LineList4Canvas });
CanvasElementList.Add(new CollectionContainer() { Collection = LabelList4Canvas });
To set up the binding the CustomLine Class is shown here. The CanvasElement and the LabelTextBlock class are set up in the same way.
CustomLine
public class CustomLine : INotifyPropertyChanged
{
private double _X1;
private double _X2;
private double _Y1;
private double _Y2;
private int _strokeThickness = 3;
private Brush _stroke = Brushes.Black;
private DoubleCollection _strokeDashArray = new DoubleCollection() { 1.0, 0.0 };
public double X1 { get { return _X1; } set { if (_X1 != value) { _X1 = value; NotifyPropertyChanged("X1"); } } }
public double X2 { get { return _X2; } set { if (_X2 != value) { _X2 = value; NotifyPropertyChanged("X2"); } } }
public double Y1 { get { return _Y1; } set { if (_Y1 != value) { _Y1 = value; NotifyPropertyChanged("Y1"); } } }
public double Y2 { get { return _Y2; } set { if (_Y2 != value) { _Y2 = value; NotifyPropertyChanged("Y2"); } } }
public int StrokeThickness { get { return _strokeThickness; } set { if (_strokeThickness != value) { _strokeThickness = value; NotifyPropertyChanged("StrokeThickness"); } } }
public Brush Stroke { get { return _stroke; } set { if (_stroke != value) { _stroke = value; NotifyPropertyChanged("Stroke"); } } }
public DoubleCollection StrokeDashArray { get { return _strokeDashArray; } set { if (_strokeDashArray != value) { _strokeDashArray = value; NotifyPropertyChanged("StrokeDashArray"); } } }
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
and finally the custom selectors are needed to use the right template and the right style for within the canvas:
public class CustomTemplateSelectors : DataTemplateSelector
{
public DataTemplate CanvasElementTemplate { get; set; }
public DataTemplate LineTemplate { get; set; }
public DataTemplate LabelTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is CanvasElement)
return CanvasElementTemplate;
else if (item is CustomLine)
return LineTemplate;
else if (item is LabelTextBlock)
return LabelTemplate;
else return base.SelectTemplate(item, container);
}
}
public class CustomStyleSelector : StyleSelector
{
public Style CanvasStyle_TL { get; set; }
public Style CanvasStyle_TR { get; set; }
public Style CanvasStyle_BL { get; set; }
public Style CanvasStyle_BR { get; set; }
public Style LineStyle { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
if (item is CanvasElement)
return CanvasStyle_TL;
else if (item is CustomLine)
return LineStyle;
else if (item is LabelTextBlock)
{
var tempItem = item as LabelTextBlock;
if (tempItem.Tag == "TL")
return CanvasStyle_TL;
else if (tempItem.Tag == "TR")
return CanvasStyle_TR;
else if (tempItem.Tag == "BL")
return CanvasStyle_BL;
else if (tempItem.Tag == "BR")
return CanvasStyle_BR;
else return base.SelectStyle(item, container);
}
else return base.SelectStyle(item, container);
}
}
Related
I have a listview with values that are being updated constantly from a different thread.
I want to change the color of the background according to the value of the item.
After reading a lot I came to the following conclusions:
The correct way to set background color for list view item is via style selector.
Style selector is called only once in the initialization of the list.
How can I achieve this simple behavior?
xaml:
<Page
x:Class="MyProject.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyProject"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<ListView ItemsSource="{x:Bind ViewModel.DataRef.Values, Mode=OneWay}" HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:ValWrapper">
<TextBlock Text="{x:Bind Val, Mode=OneWay}"/>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyleSelector>
<local:CustomItemContainerStyleSelector>
<local:CustomItemContainerStyleSelector.Bad>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="Red"/>
</Style>
</local:CustomItemContainerStyleSelector.Bad>
<local:CustomItemContainerStyleSelector.Good>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="Green"/>
</Style>
</local:CustomItemContainerStyleSelector.CloseToBad>
</local:CustomItemContainerStyleSelector>
</ListView.ItemContainerStyleSelector>
</ListView>
</Grid>
</Page>
cs:
public sealed partial class MainPage : Page
{
public ViewModel ViewModel { get; set; }
public MainPage()
{
this.InitializeComponent();
this.ViewModel = new ViewModel();
}
}
public class CustomItemContainerStyleSelector : StyleSelector
{
public Style Bad { get; set; }
public Style Good { get; set; }
protected override Style SelectStyleCore(object item, DependencyObject container)
{
double threshold = 1;
ValWrapper v = (ValWrapper)item;
if (v.Val <= threshold)
{
return Bad;
}
else {
return Good;
}
}
}
Whenever the data changes, "NotifyPropertyChanged" is called (implements INotifyPropertyChanged).
Please check the following steps:
Set a temporary variable _tempValue to record previous number.
Bind the Background property to IsUpdate, the initial value is all false.
If the number changes, please set IsUpdate to true, then the Background of ListViewItem turns red.
XAML:
<Page
x:Class="Permisson.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Permisson"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<local:ColorConverter x:Key="ColorConverter"/>
</Page.Resources>
<Grid>
<StackPanel>
<ListView ItemsSource="{x:Bind ViewModel.DataRef, Mode=OneWay}" HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate >
<DataTemplate x:DataType="local:ValWrapper">
<Grid Background="{Binding IsUpdate, Converter={StaticResource ColorConverter},Mode=OneWay}">
<TextBlock Text="{Binding Val, Mode=OneWay}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="ChangeNum" Click="Button_Click"/>
<Button Content="ChangeNum2" Click="Button_Click_1"/>
</StackPanel>
</Grid>
</Page>
Code behind:
namespace Permisson
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public ViewModel ViewModel { get; set; }
public MainPage()
{
this.InitializeComponent();
this.ViewModel = new ViewModel();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var v = ViewModel.DataRef[0];
v.Val = 9;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
var v = ViewModel.DataRef[1];
v.Val = 10;
}
}
public class ViewModel
{
private ObservableCollection<ValWrapper> dataRef = new ObservableCollection<ValWrapper>()
{
new ValWrapper {Val=22,Brush=new SolidColorBrush (Colors.Green),IsUpdate = false },
new ValWrapper {Val=25,Brush=new SolidColorBrush (Colors.Green),IsUpdate = false},
new ValWrapper {Val=35,Brush=new SolidColorBrush (Colors.Green),IsUpdate = false},
new ValWrapper {Val=45,Brush=new SolidColorBrush (Colors.Green),IsUpdate = false },
new ValWrapper {Val=55,Brush=new SolidColorBrush (Colors.Green),IsUpdate = false},
new ValWrapper {Val=65,Brush=new SolidColorBrush (Colors.Green),IsUpdate = false }
};
public ObservableCollection<ValWrapper> DataRef { get { return dataRef; } }
}
public class ColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var color = new SolidColorBrush();
if ((bool)value)
{
color.Color = Colors.Red;
}
else
{
color.Color = Colors.Green;
}
return color;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
public class ValWrapper : INotifyPropertyChanged
{
private int val;
private SolidColorBrush brush;
public SolidColorBrush Brush
{
get { return brush; }
set
{
brush = value;
RaisePropertyChanged();
}
}
private int _tempValue;
public int Val
{
get { return val; }
set
{
if(_tempValue != value && _tempValue != 0)
{
IsUpdate = true;
}
val = value;
RaisePropertyChanged();
_tempValue = val;
}
}
private bool _isUpdate;
public bool IsUpdate
{
set
{
_isUpdate = value;
RaisePropertyChanged();
}
get
{
return _isUpdate;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string propertyname = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyname));
}
}
}
}
I'm new to wpf. Trying to add a listbox in a datagrid. Everything runs perfect but the selected value binding is not working. It's not writing the SelectedValue back. Please help.
<DataGrid Name="SoruDataGrid" ItemsSource="{Binding Test.TestSonucuCollection}" Grid.Row="1" AutoGenerateColumns="False" Grid.ColumnSpan="2" >
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Soru.Id}"/>
<DataGridTextColumn Header="Soru" Binding="{Binding Soru.Text}" Width="300">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="AcceptsReturn" Value="true" />
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
<DataGridTemplateColumn Header="Cevap">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ListBox SelectionMode="Single"
ItemsSource="{Binding Soru.CevapCollection}"
DisplayMemberPath="Text"
SelectedValuePath="{Binding Id}"
SelectedValue="{Binding CevapId, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}">
CevapModel class
using System.ComponentModel;
namespace Test.Model
{
public class CevapModel : INotifyPropertyChanged
{
Cevap _cevap;
public event PropertyChangedEventHandler PropertyChanged;
public CevapModel()
{
_cevap = new Cevap();
}
public CevapModel(Cevap cevap)
{
_cevap = cevap;
}
public int Id
{
get { return _cevap.Id; }
set
{
_cevap.Id = value;
OnPropertyChanged("Id");
}
}
public int SoruId
{
get { return _cevap.SoruId; }
set
{
_cevap.SoruId = value;
OnPropertyChanged("SoruId");
}
}
public string Text
{
get { return _cevap.Text; }
set
{
_cevap.Text = value;
OnPropertyChanged("Text");
}
}
public int Puan
{
get { return _cevap.Puan; }
set
{
_cevap.Puan = value;
OnPropertyChanged("Puan");
}
}
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
}
TestSonucuModel.cs
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
namespace Test.Model
{
public class TestSonucuModel : INotifyPropertyChanged
{
TestSonucu _testSonucu;
SoruModel _soru;
public event PropertyChangedEventHandler PropertyChanged;
public TestSonucuModel()
{
_testSonucu = new TestSonucu();
}
public TestSonucuModel(TestSonucu testSonucu)
{
_testSonucu = testSonucu;
}
public int Id
{
get { return _testSonucu.Id; }
set
{
_testSonucu.Id = value;
OnPropertyChanged("Id");
}
}
public int TestId
{
get { return _testSonucu.TestId; }
set
{
_testSonucu.TestId = value;
OnPropertyChanged("TestId");
}
}
public int SoruId
{
get { return _testSonucu.SoruId; }
set
{
_testSonucu.SoruId = value;
OnPropertyChanged("SoruId");
}
}
public SoruModel Soru
{
get { return _soru; }
set
{
_soru = value;
OnPropertyChanged("Soru");
}
}
public int CevapId
{
get { return _testSonucu.CevapId; }
set
{
_testSonucu.CevapId = value;
OnPropertyChanged("CevapId");
}
}
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public static implicit operator TestSonucu(TestSonucuModel t)
{
return t._testSonucu;
}
}
}}
If, as you said, CevapId is a property of TestSonucu class which is the view model behind each row of your DataGrid you don't need to change binding context in this case as it will be set to instance of TestSonucu class
<ListBox ...
ItemsSource="{Binding Soru.CevapCollection}"
DisplayMemberPath="Text"
SelectedValuePath="Id"
SelectedValue="{Binding Path=CevapId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
also SelectedValuePathshould be just Id, in the same way as you specify DisplayMemberPath which means it will take Text property of each CevapCollection item for display and Id property as value
I have an application where a number of custom buttons are dynamically generated within a WrapPanel. All works fine and I am able to assign border thickness, ImageSource, Content etc. as I generate the buttons in code. The customer now has a requirement to allow them to choose border colours for individual buttons and try as I might I cannot figure out the correct binding scenario. I'm on a steep WPF learning curve here so it may be that my initial design is somewhat off kilter.
In my Generic.XAML I have the button specified thus:
<Style TargetType="{x:Type local:LauncherButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:LauncherButton}">
<Border Name="LauncherButtonBorder" BorderThickness="{TemplateBinding BThickness}"
CornerRadius="10" Background="White" >
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="BorderBrush" Value="SteelBlue" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="PaleGoldenrod" />
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<DockPanel LastChildFill="True" Background="White" Margin="3">
<TextBlock Text="{TemplateBinding Content}" HorizontalAlignment="Center"
Foreground="{DynamicResource TaskButtonTextBrush}" FontWeight="Bold"
Margin="5,0,0,0" VerticalAlignment="Center" FontSize="10"
Background="Transparent" DockPanel.Dock="Bottom" TextWrapping="Wrap" />
<Image Source="{TemplateBinding ImageSource}" Stretch="Uniform" />
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I want to dynamically change in c# the border colours that are currently set to static SteelBlue and PaleGoldenrod.
The button class is defined thus:
public class LauncherButton : ButtonBase
{
static LauncherButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LauncherButton), new FrameworkPropertyMetadata(typeof(LauncherButton)));
}
public ImageSource ImageSource
{
get { return (ImageSource)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
public Thickness BThickness
{
get { return (Thickness) GetValue(BThicknessProperty); }
set { SetValue(BThicknessProperty,value);}
}
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(LauncherButton), new UIPropertyMetadata(null));
public static readonly DependencyProperty BThicknessProperty =
DependencyProperty.Register("BThickness", typeof(Thickness), typeof(LauncherButton), new UIPropertyMetadata(null));
}
and I'm binding some of the properties to an instance of the following class:
public class CustomButton:INotifyPropertyChanged
{
private string _type;
private string _buttonId;
private string _name;
private string _image;
private string _link;
private string _parent;
private List<CustomButton> _children;
private bool _isExpanded;
private bool _isSelected;
public string ButtonId
{
get { return _buttonId; }
set
{
if (value == _buttonId) return;
_buttonId = value;
OnPropertyChanged("ButtonId");
}
}
public string Type
{
get { return _type; }
set
{
if (value == _type) return;
_type = value;
OnPropertyChanged("Type");
}
}
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged("Name");
}
}
public string Image
{
get { return _image; }
set
{
if (value == _image) return;
_image = value;
OnPropertyChanged("Image");
}
}
public string Link
{
get { return _link; }
set
{
if (value == _link) return;
_link = value;
OnPropertyChanged("Link");
}
}
public string Parent
{
get { return _parent; }
set
{
if (value == _parent) return;
_parent = value;
OnPropertyChanged("Parent");
}
}
public List<CustomButton> Children
{
get { return _children; }
set
{
if (Equals(value, _children)) return;
_children = value;
OnPropertyChanged("Children");
}
}
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value.Equals(_isExpanded)) return;
_isExpanded = value;
OnPropertyChanged("IsExpanded");
}
}
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value.Equals(_isSelected)) return;
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Are you trying to make the two brushes used for Border.BorderBrush dynamic?
If so you can address it in a few ways.
Add two dependency properties to LauncherButton for say NormalBorderBrush and MouseOverBorderBrush and then set it as you wish when you use the Button. Now to get the Border to use this, within it's Style where you set SteelBlue or PaleGoldenRod, apply a RelativeSource FindAncestor binding with AncestorType as local:LauncherButton and point it to the corresponding Brush(NormalBorderBrush or MouseOverBorderBrush)
Example:
public class LauncherButton : ButtonBase {
...
public static readonly DependencyProperty NormalBorderBrushProperty =
DependencyProperty.Register("NormalBorderBrush", typeof(Brush), typeof(LauncherButton),
new UIPropertyMetadata(Brushes.Blue));
public static readonly DependencyProperty MouseOverBorderBrushProperty =
DependencyProperty.Register("MouseOverBorderBrush", typeof(Brush), typeof(LauncherButton),
new UIPropertyMetadata(Brushes.Red));
public Brush NormalBorderBrush
{
get { return (Brush)GetValue(NormalBorderBrushProperty); }
set { SetValue(NormalBorderBrushProperty, value); }
}
public Brush MouseOverBorderBrush
{
get { return (Brush)GetValue(MouseOverBorderBrushProperty); }
set { SetValue(MouseOverBorderBrushProperty, value); }
}
}
in xaml:
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="BorderBrush"
Value="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type local:LauncherButton}},
Path=NormalBorderBrush}" />
<Style.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="BorderBrush"
Value="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type local:LauncherButton}},
Path=MouseOverBorderBrush}" />
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
and usage:
<local:LauncherButton BThickness="5"
Content="Hellooooo"
MouseOverBorderBrush="Green"
NormalBorderBrush="Aqua" />
Sample Download - This does not contain the converter for Brush usage, that should be easy enough to implement.
OR You could have two brushes defined as dynamic resources and override their color's from your code when you need to.
OR You can use the Button's BorderBrush property which it already has and apply this to the Border with a TemplateBinding BorderBrush. Now this would mean you need to switch the BorderBrush accordingly when your IsMouseOver state change occurs.
OR you could even go to extents of retrieving the button's Style and getting a reference to the Border element by finding it via it's Name and then tweaking it at run-time.
Personally I'd opt for option 1. Finally use a converter or likewise in the Binding to make it MVVM friendly.
I have a board with clickable labels (Grass and Unit), When I click a Grass label I it should move the Unit Label to the Grass's x and y position. It works, but kinda wrong. When I click on a label, nothing happens until I move the cursor out of the clicked label, then the wanted behaviour executes.
XAML
<local:Grass Grid.Row="9" Grid.Column="16" />
<local:Unit Grid.Row="{Binding Path=xPos, UpdateSourceTrigger=PropertyChanged}" Grid.Column="{Binding Path=yPos, UpdateSourceTrigger=PropertyChanged}" >
<local:Unit.Background>
<ImageBrush ImageSource="Images/tjej.png"/>
</local:Unit.Background>
</local:Unit>
ObjectInspector
public class ObjectInspector : INotifyPropertyChanged
{
private int _xPos = 1, _yPos = 2;
public int xPos
{
get { return _xPos; }
set
{
_xPos = value;
NotifyPropertyChanged("xPos");
}
}
public int yPos
{
get { return _yPos; }
set {
_yPos = value;
NotifyPropertyChanged("yPos");
}
}
private string _type = "none";
public string type
{
get { return _type; }
set {
_type = value;
NotifyPropertyChanged("type");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
System.Diagnostics.Debug.WriteLine("property changed");
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Grass
public class Grass : Button
{
protected override void OnClick()
{
base.OnClick();
int x = (int)this.GetValue(Grid.RowProperty);
int y = (int)this.GetValue(Grid.ColumnProperty);
string type = this.GetType().Name;
MainWindow.objectInspector.xPos = x;
MainWindow.objectInspector.yPos = y;
MainWindow.objectInspector.type = type;
}
}
MainWindow
public partial class MainWindow : Window
{
public static ObjectInspector objectInspector= new ObjectInspector();
public MainWindow()
{
InitializeComponent();
this.DataContext = objectInspector;
}
}
Any ideas?
Edit
Added MainWindow and Grass
EDIT
Try to register to the common event handler Click of buttons:
<local:Grass Grid.Row="9" Grid.Column="16" Click="ClickEventHandler" />
...
And take the grass element from the sender, in the event handler method.
Anyway, I think a better way for doing this is usin MVVM patter. You may set a GrassViewModel and UnitViewModel. Then create a DataTemplate for each one. For example:
<DataTemplate DataType="{x:Type ViewModel:UnitViewModel}">
...Visual Elements Here...
</DataTemplate>
The for showing the elements in a grid you may use a ListBox with a Grid as items panel, some like this:
<ListBox ItemsSource={Binding AllItemsCollection}>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
...rows and columns definitions here...
</Grid>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<!--HERE THE ITEMS STYLE, HERE YOU SET THE COLUMN, ROW BINDINGS-->
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Grid.Row" Value="{Binding yPos}"/>
<Setter Property="Grid.Column" Value="{Binding xPos}"/>
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Then you only need to create the AllItemsCollection in your view model with all the elements that you want. You can handler the click event using behaviors, or creating a UserControl for the grass (and controlling the click event inside):
<DataTemplate DataType="{x:Type ViewModel:UnitViewModel}">
<GrassUserControl ...Inside the grass user control you can handler the click event.../>
</DataTemplate>
Hope helps...
If your following MVVM then you can attach a property to the label as below. You can attache this behavior any control that derives from UIElement
Create a Attached property for MouseClick
public class MouseClick
{
public static readonly DependencyProperty MouseLeftClickProperty =
DependencyProperty.RegisterAttached("MouseLeftClick", typeof(ICommand), typeof(MouseClick),
new FrameworkPropertyMetadata(CallBack));
public static void SetMouseLeftClick(DependencyObject sender, ICommand value)
{
sender.SetValue(MouseLeftClickProperty, value);
}
public static ICommand GetMouseLeftClick(DependencyObject sender)
{
return sender.GetValue(MouseLeftClickProperty) as ICommand;
}
public static readonly DependencyProperty MouseEventParameterProperty =
DependencyProperty.RegisterAttached(
"MouseEventParameter",
typeof(object),
typeof(MouseClick),
new FrameworkPropertyMetadata((object)null, null));
public static object GetMouseEventParameter(DependencyObject d)
{
return d.GetValue(MouseEventParameterProperty);
}
public static void SetMouseEventParameter(DependencyObject d, object value)
{
d.SetValue(MouseEventParameterProperty, value);
}
private static void CallBack(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender != null)
{
UIElement element = sender as UIElement;
if (element != null)
{
if (e.OldValue != null)
{
element.RemoveHandler(UIElement.MouseDownEvent, new MouseButtonEventHandler(Handler));
}
if (e.NewValue != null)
{
element.AddHandler(UIElement.MouseDownEvent, new MouseButtonEventHandler(Handler), true);
}
}
}
}
private static void Handler(object sender, EventArgs e)
{
UIElement element = sender as UIElement;
if (sender != null)
{
ICommand cmd = element.GetValue(MouseLeftClickProperty) as ICommand;
if (cmd != null)
{
RoutedCommand routedCmd =cmd as RoutedCommand;
object paramenter = element.GetValue(MouseEventParameterProperty);
if (paramenter == null)
{
paramenter = element;
}
if (routedCmd != null)
{
if (routedCmd.CanExecute(paramenter, element))
{
routedCmd.Execute(paramenter, element);
}
}
else
{
if (cmd.CanExecute(paramenter))
{
cmd.Execute(paramenter);
}
}
}
}
}
}
In you Xaml attache the Command of your viewModel as below
<Label Height="30" Width="200" Margin="10" Content="Click" local:MouseClick.MouseLeftClick="{Binding Click}" />
How would you make a line slowly draw across the screen?
I am trying to animate a line on a canvas in a C#/WPF project.
I would like to use C# code and not XAML.
I Have a running sample that uses the MVVM Pattern and creates Lines within a ListBox that has a Canvas as its ItemsPanel.
I actually made it for this question, but the OP kind of dissapeared and never contacted me about it.
This is what it looks like in my computer:
The main part of it is this:
<ListBox ItemsSource="{Binding}" x:Name="lst" Height="500" Width="500">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="FocusVisualStyle">
<Setter.Value>
<Style TargetType="Control">
<Setter Property="Opacity" Value="0"/>
</Style>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Line X1="{Binding X1}" Y1="{Binding Y1}"
X2="{Binding X2}" Y2="{Binding Y2}"
StrokeThickness="{Binding Thickness}"
Opacity="{Binding Opacity}"
x:Name="Line">
<Line.Stroke>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="{Binding Color1}" Offset="0"/>
<GradientStop Color="{Binding Color2}" Offset="1"/>
</LinearGradientBrush>
</Line.Stroke>
</Line>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Effect" TargetName="Line">
<Setter.Value>
<DropShadowEffect Color="CornflowerBlue" ShadowDepth="3" BlurRadius="10"/>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
ViewModel:
public class LineViewModel : INotifyPropertyChanged
{
#region Timer-based Animation
private System.Threading.Timer Timer;
private static Random Rnd = new Random();
private bool _animate;
public bool Animate
{
get { return _animate; }
set
{
_animate = value;
NotifyPropertyChanged("Animate");
if (value)
StartTimer();
else
StopTimer();
}
}
private int _animationSpeed = 1;
public int AnimationSpeed
{
get { return _animationSpeed; }
set
{
_animationSpeed = value;
NotifyPropertyChanged("AnimationSpeed");
if (Timer != null)
Timer.Change(0, 100/value);
}
}
private static readonly List<int> _animationSpeeds = new List<int>{1,2,3,4,5};
public List<int> AnimationSpeeds
{
get { return _animationSpeeds; }
}
public void StartTimer()
{
StopTimer();
Timer = new Timer(x => Timer_Tick(), null, 0, 100/AnimationSpeed);
}
public void StopTimer()
{
if (Timer != null)
{
Timer.Dispose();
Timer = null;
}
}
private void Timer_Tick()
{
X1 = X1 + Rnd.Next(-2, 3);
Y1 = Y1 + Rnd.Next(-2, 3);
X2 = X2 + Rnd.Next(-2, 3);
Y2 = Y2 + Rnd.Next(-2, 3);
}
#endregion
#region Coordinates
private double _x1;
public double X1
{
get { return _x1; }
set
{
_x1 = value;
NotifyPropertyChanged("X1");
}
}
private double _y1;
public double Y1
{
get { return _y1; }
set
{
_y1 = value;
NotifyPropertyChanged("Y1");
}
}
private double _x2;
public double X2
{
get { return _x2; }
set
{
_x2 = value;
NotifyPropertyChanged("X2");
}
}
private double _y2;
public double Y2
{
get { return _y2; }
set
{
_y2 = value;
NotifyPropertyChanged("Y2");
}
}
#endregion
#region Other Properties
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
private double _thickness;
public double Thickness
{
get { return _thickness; }
set
{
_thickness = value;
NotifyPropertyChanged("Thickness");
}
}
public Color Color1 { get; set; }
public Color Color2 { get; set; }
private double _opacity = 1;
public double Opacity
{
get { return _opacity; }
set
{
_opacity = value;
NotifyPropertyChanged("Opacity");
}
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
Application.Current.Dispatcher.BeginInvoke((Action)(() =>
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}));
}
#endregion
}
Edit: Source code now on GitHub
You will need to use a Storyboard and animate the Line.X2 and Line.Y2 Properties. See if this works for you.
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Canvas Name="myCanvas">
<Button Canvas.Left="248" Canvas.Top="222" Content="Button" Height="23" Name="button1" Width="75" Click="button1_Click" />
</Canvas>
</Window>
Button Click Event
private void button1_Click(object sender, RoutedEventArgs e)
{
Line line = new Line();
myCanvas.Children.Add(line);
line.Stroke = Brushes.Red;
line.StrokeThickness = 2;
line.X1 = 0;
line.Y1 = 0;
Storyboard sb = new Storyboard();
DoubleAnimation da = new DoubleAnimation(line.Y2 , 100, new Duration(new TimeSpan(0, 0, 1)));
DoubleAnimation da1 = new DoubleAnimation(line.X2, 100, new Duration(new TimeSpan(0, 0, 1)));
Storyboard.SetTargetProperty(da, new PropertyPath("(Line.Y2)"));
Storyboard.SetTargetProperty(da1, new PropertyPath("(Line.X2)"));
sb.Children.Add(da);
sb.Children.Add(da1);
line.BeginStoryboard(sb);
}