C# WPF Bindings not updating until MouseOut - c#

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}" />

Related

How to properly set up a TwoWay Binding in wpf with Code

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);
}
}

How to set a Binding from ItemTemplate to the hosting Container in an ItemsControl? (UWP)

Given an arbitrary ItemsControl, like a ListView, I want to set a Binding from inside the ItemsTemplate to the hosting Container. How can I do that easily? For example, in WPF we can do it using this inside the ItemTemplate
<ListView.ItemTemplate>
<DataTemplate>
<SomeControl Property="{Binding Path=TargetProperty, RelativeSouce={RelativeSource FindAncestor, AncestorType={x:Type MyContainer}}}" />
</DataTemplate>
<ListView.ItemTemplate>
In this example (for WPF) the Binding will be set between Property in SomeControl and TargetProperty of the ListViewItem (implicit, because it will be generated dynamically by the ListView to host the each of its items).
How can we do achieve the same in UWP?
I want something that is MVVM-friendly. Maybe with attached properties or an Interaction Behavior.
When the selection changes, search the visual tree for the radio button with the DataContext corresponding to selected/deselected items. Once it's found, you can check/uncheck at your leisure.
I have a toy model object looking like this:
public class Data
{
public string Name { get; set; }
}
My Page is named self and contains this collection property:
public Data[] Data { get; set; } =
{
new Data { Name = "One" },
new Data { Name = "Two" },
new Data { Name = "Three" },
};
The list view, binding to the above collection:
<ListView
ItemsSource="{Binding Data, ElementName=self}"
SelectionChanged="OnSelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The SelectionChanged event handler:
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListView lv = sender as ListView;
var removed = FindRadioButtonWithDataContext(lv, e.RemovedItems.FirstOrDefault());
if (removed != null)
{
removed.IsChecked = false;
}
var added = FindRadioButtonWithDataContext(lv, e.AddedItems.FirstOrDefault());
if (added != null)
{
added.IsChecked = true;
}
}
Finding the radio button with a DataContext matching our Data instance:
public static RadioButton FindRadioButtonWithDataContext(
DependencyObject parent,
object data)
{
if (parent != null)
{
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
ListViewItem lv = child as ListViewItem;
if (lv != null)
{
RadioButton rb = FindVisualChild<RadioButton>(child);
if (rb?.DataContext == data)
{
return rb;
}
}
RadioButton childOfChild = FindRadioButtonWithDataContext(child, data);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return null;
}
And finally, a helper method to find a child of a specific type:
public static T FindVisualChild<T>(
DependencyObject parent)
where T : DependencyObject
{
if (parent != null)
{
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
T candidate = child as T;
if (candidate != null)
{
return candidate;
}
T childOfChild = FindVisualChild<T>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return default(T);
}
The result:
This will break if a given model instance shows up more than once in the list.
Note: this answer is based on WPF, there might be some changes necessary for UWP.
There are basically two cases to consider:
You have a data driven aspect that needs to be bound to the item container
You have a view-only property
Lets assume a customized listview for both cases:
public class MyListView : ListView
{
protected override DependencyObject GetContainerForItemOverride()
{
return new DesignerItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is DesignerItem;
}
}
public class DesignerItem : ListViewItem
{
public bool IsEditing
{
get { return (bool)GetValue(IsEditingProperty); }
set { SetValue(IsEditingProperty, value); }
}
public static readonly DependencyProperty IsEditingProperty =
DependencyProperty.Register("IsEditing", typeof(bool), typeof(DesignerItem));
}
In case 1, you can use the ItemContainerStyle to link your viewmodel property with a binding and then bind the same property inside the datatemplate
class MyData
{
public bool IsEditing { get; set; } // also need to implement INotifyPropertyChanged here!
}
XAML:
<local:MyListView ItemsSource="{Binding Source={StaticResource items}}">
<local:MyListView.ItemContainerStyle>
<Style TargetType="{x:Type local:DesignerItem}">
<Setter Property="IsEditing" Value="{Binding IsEditing,Mode=TwoWay}"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</local:MyListView.ItemContainerStyle>
<local:MyListView.ItemTemplate>
<DataTemplate>
<Border Background="Red" Margin="5" Padding="5">
<CheckBox IsChecked="{Binding IsEditing}"/>
</Border>
</DataTemplate>
</local:MyListView.ItemTemplate>
</local:MyListView>
In case 2, it appears that you don't really have a data driven property and consequently, the effects of your property should be reflected within the control (ControlTemplate).
In the following example a toolbar is made visible based on the IsEditing property. A togglebutton can be used to control the property, the ItemTemplate is used as an inner element next to the toolbar and button, it knows nothing of the IsEditing state:
<local:MyListView ItemsSource="{Binding Source={StaticResource items}}">
<local:MyListView.ItemContainerStyle>
<Style TargetType="{x:Type local:DesignerItem}">
<Setter Property="IsEditing" Value="{Binding IsEditing,Mode=TwoWay}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:DesignerItem}">
<DockPanel>
<ToggleButton DockPanel.Dock="Right" Margin="5" VerticalAlignment="Top" IsChecked="{Binding IsEditing,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}" Content="Edit"/>
<!--Toolbar is something control related, rather than data related-->
<ToolBar x:Name="MyToolBar" DockPanel.Dock="Top" Visibility="Collapsed">
<Button Content="Tool"/>
</ToolBar>
<ContentPresenter ContentSource="Content"/>
</DockPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsEditing" Value="True">
<Setter TargetName="MyToolBar" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</local:MyListView.ItemContainerStyle>
<local:MyListView.ItemTemplate>
<DataTemplate>
<Border Background="Red" Margin="5" Padding="5">
<TextBlock Text="Hello World"/>
</Border>
</DataTemplate>
</local:MyListView.ItemTemplate>
</local:MyListView>
For a better control template, you may chose to use Blend and create the control template starting at the full ListViewItem template and just editing your changes into it.
If your DesignerItem generally has a specific enhanced appearance, consider designing it in the Themes/Generic.xaml with the appropriate default style.
As commented, you could provide a separate data template for the editing mode. To do this, add a property to MyListView and to DesignerItem and use MyListView.PrepareContainerForItemOverride(...) to transfer the template.
In order to apply the template without the need for Setter.Value bindings, you can use value coercion on DesignerItem.ContentTemplate based on IsEditing.
public class MyListView : ListView
{
protected override DependencyObject GetContainerForItemOverride()
{
return new DesignerItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is DesignerItem;
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var elem = element as DesignerItem;
elem.ContentEditTemplate = ItemEditTemplate;
}
public DataTemplate ItemEditTemplate
{
get { return (DataTemplate)GetValue(ItemEditTemplateProperty); }
set { SetValue(ItemEditTemplateProperty, value); }
}
public static readonly DependencyProperty ItemEditTemplateProperty =
DependencyProperty.Register("ItemEditTemplate", typeof(DataTemplate), typeof(MyListView));
}
public class DesignerItem : ListViewItem
{
static DesignerItem()
{
ContentTemplateProperty.OverrideMetadata(typeof(DesignerItem), new FrameworkPropertyMetadata(
null, new CoerceValueCallback(CoerceContentTemplate)));
}
private static object CoerceContentTemplate(DependencyObject d, object baseValue)
{
var self = d as DesignerItem;
if (self != null && self.IsEditing)
{
return self.ContentEditTemplate;
}
return baseValue;
}
private static void OnIsEditingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(ContentTemplateProperty);
}
public bool IsEditing
{
get { return (bool)GetValue(IsEditingProperty); }
set { SetValue(IsEditingProperty, value); }
}
public static readonly DependencyProperty IsEditingProperty =
DependencyProperty.Register("IsEditing", typeof(bool), typeof(DesignerItem), new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsEditingChanged)));
public DataTemplate ContentEditTemplate
{
get { return (DataTemplate)GetValue(ContentEditTemplateProperty); }
set { SetValue(ContentEditTemplateProperty, value); }
}
// Using a DependencyProperty as the backing store for ContentEditTemplate. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ContentEditTemplateProperty =
DependencyProperty.Register("ContentEditTemplate", typeof(DataTemplate), typeof(DesignerItem));
}
Note, for an easier example I will activate the "edit" mode by ListViewItem.IsSelected with some trigger:
<local:MyListView ItemsSource="{Binding Source={StaticResource items}}">
<local:MyListView.ItemContainerStyle>
<Style TargetType="{x:Type local:DesignerItem}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="IsEditing" Value="True"/>
</Trigger>
</Style.Triggers>
</Style>
</local:MyListView.ItemContainerStyle>
<local:MyListView.ItemTemplate>
<DataTemplate>
<Border Background="Red" Margin="5" Padding="5">
<TextBlock Text="Hello World"/>
</Border>
</DataTemplate>
</local:MyListView.ItemTemplate>
<local:MyListView.ItemEditTemplate>
<DataTemplate>
<Border Background="Green" Margin="5" Padding="5">
<TextBlock Text="Hello World"/>
</Border>
</DataTemplate>
</local:MyListView.ItemEditTemplate>
</local:MyListView>
Intended behavior: the selected item becomes edit-enabled, getting the local:MyListView.ItemEditTemplate (green) instead of the default template (red)
Just in case you might want to have an IsSelected property in your view model item class, you may create a derived ListView that establishes a Binding of its ListViewItems to the view model property:
public class MyListView : ListView
{
public string ItemIsSelectedPropertyName { get; set; } = "IsSelected";
protected override void PrepareContainerForItemOverride(
DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
BindingOperations.SetBinding(element,
ListViewItem.IsSelectedProperty,
new Binding
{
Path = new PropertyPath(ItemIsSelectedPropertyName),
Source = item,
Mode = BindingMode.TwoWay
});
}
}
You might now simply bind the RadioButton's IsChecked property in the ListView's ItemTemplate to the same view model property:
<local:MyListView ItemsSource="{Binding DataItems}">
<ListView.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Content}"
IsChecked="{Binding IsSelected, Mode=TwoWay}"/>
</DataTemplate>
</ListView.ItemTemplate>
</local:MyListView>
In the above example the data item class also has Content property. Obviously, the IsSelected property of the data item class must fire a PropertyChanged event.

WPF DataGrid: Set ColumnWidth in a Trigger

I'm having problems to set the ColumnWidth of a DataGrid inside a trigger in his style.
I have this:
<DataGrid ItemsSource="{Binding Data}">
<DataGrid.Style>
<Style TargetType="DataGrid">
<Style.Triggers>
<DataTrigger Binding="{Binding Data.Count}" Value="2">
<Setter Property="Background" Value="LightGreen" />
<Setter Property="ColumnWidth" Value="400" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
</DataGrid>
In case of 2 rows I want to fill the background with green and to make wider columns, but I can't only achieve the green background.. Why the ColumnWidth setting is not working?
[![enter image description here][1]][1]
It works if I put the ColumnWidth setting outside the Trigger.. but I don't want this..
<DataGrid ItemsSource="{Binding Data}">
<DataGrid.Style>
<Style TargetType="DataGrid">
<Setter Property="ColumnWidth" Value="400" />
<Style.Triggers>
<DataTrigger Binding="{Binding Data.Count}" Value="2">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
</DataGrid>
Thanks!
Solved:
Finally I have established ColumnWidt with a Binding to my data and a converter:
<DataGrid ColumnWidth="{Binding Data, Converter={StaticResource DataToColumnWidthConverter}}" ItemsSource="{Binding Data}" IsReadOnly="True" MaxHeight="300" >
Converter:
[ValueConversion(typeof(DataTable), typeof(DataGridLength))]
public class DataToColumnWidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
DataTable dt = value as DataTable;
if (dt != null && dt.Rows.Count == 2)
{
return new DataGridLength(400);
}
return new DataGridLength();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
This is OK for me because my data won't change number of rows in execution time, so ColumnWidth only needs to be calculated one time at beginning.
Thanks all
Remove ItemsSource from your DataGrid, and move it into DataTrigger.
To set ColumnWidth after initial binding, re-binding is needed. You cannot set ColumnWidth from code, it won't have any effect. For ColumnWidth to have any effect, you need to first remove DataGrid's DataContext/ItemsSource (setting to null) and then re-assign it.
So, if you change your collection somewhere, you have to first set the DataContext of DataGrid to null, and then re-assign it. See what I have done in Button click below.
Code below is self explanatory. I have written a converter and MarkupExtension for cases when Data.Count will not be 4.
<DataGrid x:Name="Dgrid" Margin="0,58,0,0">
<DataGrid.Style>
<Style TargetType="DataGrid">
<Style.Triggers>
<DataTrigger Binding="{Binding Data.Count}" Value="4">
<Setter Property="ColumnWidth" Value="200" />
<Setter Property="ItemsSource" Value="{Binding Data}" />
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
<DataTrigger Binding="{Binding Data.Count, Converter={local:CountToBool}}" Value="true">
<Setter Property="ColumnWidth" Value="100" />
<Setter Property="ItemsSource" Value="{Binding Data}" />
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
</DataGrid>
Converter :
public class CountToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((int)value != 4)
return true;
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class CountToBoolExtension : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return new CountToBoolConverter();
}
}
CodeBehind :
ViewModel vm = new ViewModel();
// removing 2 items and reassigning DataContext to viewmodel.
private void Button_Click(object sender, RoutedEventArgs e)
{
Dgrid.DataContext = null;
vm.Students.RemoveAt(1);
vm.Students.RemoveAt(2);
Dgrid.DataContext = vm;
}
Above code will change ColumnWidth depending upon the Data.Count value and works correctly if we change number of records in Collection at runtime.
please try the next solution. As you can see I've used a proxy object to pass a main data context to each data grid cell. In addition there is a DataTrigger which works when a Visibility of a hidden column is changed and there is an attached property that helps to control the actual column width.
Here is the code:
Xaml Code
<Window x:Class="DataGridSoHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dataGridSoHelpAttempt="clr-namespace:DataGridSoHelpAttempt"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Title="MainWindow" Height="350" Width="525" x:Name="This">
<Window.DataContext>
<dataGridSoHelpAttempt:MainViewModel/>
</Window.DataContext>
<Grid x:Name="MyGrid">
<Grid.Resources>
<dataGridSoHelpAttempt:FreezableProxyClass x:Key="ProxyElement" ProxiedDataContext="{Binding Source={x:Reference This}, Path=DataContext}"/>
</Grid.Resources>
<DataGrid x:Name="MyDataGrid" ItemsSource="{Binding DataSource}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Description" Binding="{Binding Description}" Visibility="{Binding Source={StaticResource ProxyElement},
Path=ProxiedDataContext.Visibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<DataGridTextColumn Header="Comments" Binding="{Binding Comments}"/>
<DataGridTextColumn Header="Price (click to see total)" Binding="{Binding Price, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataGrid.Columns>
<DataGrid.Resources>
<Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource ProxyElement},
Path=ProxiedDataContext.Visibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="Visible">
<Setter Property="Width" Value="200"></Setter>
<Setter Property="dataGridSoHelpAttempt:DataGridAttached.ColumnActualWidth" Value="200"/>
</DataTrigger>
<DataTrigger Binding="{Binding Source={StaticResource ProxyElement},
Path=ProxiedDataContext.Visibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="Collapsed">
<Setter Property="Width" Value="400"></Setter>
<Setter Property="dataGridSoHelpAttempt:DataGridAttached.ColumnActualWidth" Value="400"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Bottom">
<Button Content="Show Description" Command="{Binding Command}"></Button>
</StackPanel>
</Grid></Window>
Attached Property Code
public class DataGridAttached
{
public static readonly DependencyProperty ColumnActualWidthProperty = DependencyProperty.RegisterAttached(
"ColumnActualWidth", typeof (double), typeof (DataGridAttached), new PropertyMetadata(default(double), ColumnActualWidthPropertyChanged));
private static void ColumnActualWidthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var data = d.FindParent<DataGrid>();
var control = (d as Control);
if(data == null || control == null) return;
data.Columns.ToList().ForEach(column =>
{
var cellWidth = control.Width;
if(double.IsNaN(cellWidth) || double.IsInfinity(cellWidth)) return;
column.Width = cellWidth;
});
}
public static void SetColumnActualWidth(DependencyObject element, double value)
{
element.SetValue(ColumnActualWidthProperty, value);
}
public static double GetColumnActualWidth(DependencyObject element)
{
return (double) element.GetValue(ColumnActualWidthProperty);
}
}
View Model and Model
public class MainViewModel:BaseObservableObject
{
private Visibility _visibility;
private ICommand _command;
private Visibility _totalsVisibility;
private double _totalValue;
private double _columnWidth;
public MainViewModel()
{
Visibility = Visibility.Collapsed;
TotalsVisibility = Visibility.Collapsed;
DataSource = new ObservableCollection<BaseData>(new List<BaseData>
{
new BaseData {Name = "Uncle Vania", Description = "A.Chekhov, play", Comments = "worth reading", Price = 25},
new BaseData {Name = "Anna Karenine", Description = "L.Tolstoy, roman", Comments = "worth reading", Price = 35},
new BaseData {Name = "The Master and Margarita", Description = "M.Bulgakov, novel", Comments = "worth reading", Price = 56},
});
}
public ICommand Command
{
get
{
return _command ?? (_command = new RelayCommand(VisibilityChangingCommand));
}
}
private void VisibilityChangingCommand()
{
Visibility = Visibility == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed;
ColumnWidth = Visibility == Visibility.Visible ? 200d : 400d;
}
public ObservableCollection<BaseData> DataSource { get; set; }
public Visibility Visibility
{
get { return _visibility; }
set
{
_visibility = value;
OnPropertyChanged();
}
}
public ObservableCollection<BaseData> ColumnCollection
{
get { return DataSource; }
}
public Visibility TotalsVisibility
{
get { return _totalsVisibility; }
set
{
_totalsVisibility = value;
OnPropertyChanged();
}
}
public double TotalValue
{
get { return ColumnCollection.Sum(x => x.Price); }
}
public double ColumnWidth
{
get { return _columnWidth; }
set
{
_columnWidth = value;
OnPropertyChanged();
}
}
}
public class BaseData:BaseObservableObject
{
private string _name;
private string _description;
private string _comments;
private int _price;
public virtual string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
public virtual object Description
{
get { return _description; }
set
{
_description = (string) value;
OnPropertyChanged();
}
}
public string Comments
{
get { return _comments; }
set
{
_comments = value;
OnPropertyChanged();
}
}
public int Price
{
get { return _price; }
set
{
_price = value;
OnPropertyChanged();
}
}
}
Freezable Helper
public class FreezableProxyClass : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new FreezableProxyClass();
}
public static readonly DependencyProperty ProxiedDataContextProperty = DependencyProperty.Register(
"ProxiedDataContext", typeof (object), typeof (FreezableProxyClass), new PropertyMetadata(default(object)));
public object ProxiedDataContext
{
get { return (object) GetValue(ProxiedDataContextProperty); }
set { SetValue(ProxiedDataContextProperty, value); }
}
}
Helpers
public static class VisualTreeHelperExtensions
{
public static T FindParent<T>(this DependencyObject child) where T : DependencyObject
{
while (true)
{
//get parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
return parent;
child = parentObject;
}
}
}
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
public class RelayCommand : ICommand
{
private readonly Func<bool> _canExecute;
private readonly Action _execute;
public RelayCommand(Action execute)
: this(() => true, execute)
{
}
public RelayCommand(Func<bool> canExecute, Action execute)
{
_canExecute = canExecute;
_execute = execute;
}
public bool CanExecute(object parameter = null)
{
return _canExecute();
}
public void Execute(object parameter = null)
{
_execute();
}
public event EventHandler CanExecuteChanged;
}
public class RelayCommand<T> : ICommand
where T:class
{
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public RelayCommand(Action<T> execute):this(obj => true, execute)
{
}
public RelayCommand(Predicate<T> canExecute, Action<T> execute)
{
_canExecute = canExecute;
_execute = execute;
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter as T);
}
public void Execute(object parameter)
{
_execute(parameter as T);
}
public event EventHandler CanExecuteChanged;
}
It is complete test solution, you should take just the idea of how this is working. I'll be glad to help if you will have problems with the code.
Regards.

WPF Binding Border Color in Custom Button

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.

Simple DataTrigger not Working. Why?

I am trying to change the foreground color of a text block based on a bool property that its value is changed when a button is clicked. However, for some reason this is not working. Also, do I have to add the bool property to a list in first place? I tried adding the bool property directly to the DataContext, but this did not work either. Any help would be appreciated.
public static bool IsOn { get; set; }
public static List<bool> boo;
private void Button_Click(object sender, RoutedEventArgs e)
{
IsOn = true;
boo = new List<bool>();
boo.Add(IsOn);
DataContext = boo;
}
<Window.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Green" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsOn}" Value="true">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<Button Click="Button_Click" Content="Change Color" />
<TextBlock Name="textBlockColor" Text="My Foreground Color" />
</StackPanel>
First of all your class must implement INotifyPropertyChanged (msdn) to notify view that your property was changed.
Second you must assign DataContext in MainWindow constructor.
Example:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private bool _isOn;
public bool IsOn
{
get { return _isOn; }
set { _isOn = value; OnPropertyChanged("IsOn"); }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
IsOn = !IsOn;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propName));
}
}
Your XAML code is OK.
When your binding doesn't work you should use snoop in the future.

Categories