I have a label control where I use a converter to switch its styles based on a bool property, IsCheckedOut on my viewmodel, like so:
<UserControl.Resources>
<Converters:BooleanStyleConverter
x:Key="BooleanStyleConverter "
StyleFalse="{StaticResource HeaderLabelStyle}"
StyleTrue="{StaticResource HeaderLabelHighlightedStyle}" />
</UserControl.Resources>
<Label Style="{Binding Path=IsCheckedOut,
Converter={StaticResource BooleanStyleConverter}}">
some content here
</Label>
The converter simply returns one of the two styles:
public class BooleanStyleConverter : IValueConverter
{
public Style StyleFalse { get; set; }
public Style StyleTrue { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)value)
{
return StyleTrue;
}
return StyleFalse;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And the styles look something like this:
<Style x:Key="HeaderLabelHighlightedStyle" TargetType="{x:Type Label}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<Border Background="{StaticResource RedGradient}">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="HeaderLabelHighlightedStyle" TargetType="{x:Type Label}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<Border Background="{StaticResource BlueGradient}">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
So when IsCheckedOut is true the label gets a red background, and when it's false it gets a blue background (well, the styles are a bit more complicated, but you get the idea).
Now, I'd like to have a transition between the styles, so that the new colors fade in when IsCheckedOut changes.
Does anyone know how I can accomplish this?
Sorry, but you're doing it wrong.
You do get bonus points for being extremely creative and ambitious in your solution. But you've taken the proverbial 5kg hammer down on a thumbtack.
The correct solution in this situation is to use Storyboards nested in VSM States.
It looks like you essentially have 2 States for your UI: One where some business logic value is true and another state for when it's false. Note that the aforementioned distinction is 100% technology independent. In any technology, whatever it is you're trying to achieve would be considered 2 states for your UI.
In Silverlight/WPF, instead of hacking together something that mimics UI states, you could actually create VisualStateManager states.
Technically it would work in the following way:
1. Your UserControl would have 1 VisualStateGroup that has 2 VisualStates (one for true and another for false).
2. Those VSM states each represent 1 storyboard.
3. That storyboard would change the template or any other properties you feel are appropriate.
To learn the basics of VSM I strongly suggest you spend the next 30 minutes watching the following VSM videos: http://expression.microsoft.com/en-us/cc643423.aspx (Under "How Do I?")
Seriously, these videos are phenomenally successful in explaining VSM. The one that most pertinent to your dilemma is "Add States to a Control" but I'll suggest you watch all of them.
In WPF, you could use the VisualStateManager from the WPF Toolkit.
As Justin said, you are doing something wrong, and you might want to do five steps back and reconsider your approach...
But I really liked this puzzle :). I've solved it without VSM, just to demonstrate how flexible WPF is. Basic principle here is using Dependency properties value coercion. We track all style changes, but we don't let the new value go out from Coerce() function, until we complete transition animation.
To simplify your testing, just copy/paste the following code and check if it works for you :). If you want to get into details - feel free to ask additional questions.
XAML:
<Window x:Class="WpfApplication5.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:WpfApplication5"
Title="Fade Styles"
Width="320"
Height="240">
<Window.Resources>
<SolidColorBrush x:Key="RedGradient" Color="Red" />
<SolidColorBrush x:Key="BlueGradient" Color="Blue" />
<Style x:Key="HeaderLabelStyle" TargetType="{x:Type Label}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<Border Background="{StaticResource RedGradient}">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="HeaderLabelHighlightedStyle" TargetType="{x:Type Label}">
<Setter Property="Foreground" Value="White" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<Border Background="{StaticResource BlueGradient}">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<loc:BooleanStyleConverter x:Key="BooleanStyleConverter"
StyleFalse="{StaticResource HeaderLabelStyle}"
StyleTrue="{StaticResource HeaderLabelHighlightedStyle}" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Style="{Binding IsChecked, ElementName=chkToggle, Converter={StaticResource BooleanStyleConverter}}"
loc:StyleAnimation.IsActive="True"
Content="Some content here" />
<CheckBox Grid.Row="1" Name="chkToggle" Content="Use Blue" />
</Grid>
</Window>
Take a look here on loc:StyleAnimation.IsActive="True".
C#
using System;
using System.Collections;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media.Animation;
namespace WpfApplication5
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
public class StyleAnimation : DependencyObject
{
private const int DURATION_MS = 200;
private static readonly Hashtable _hookedElements = new Hashtable();
public static readonly DependencyProperty IsActiveProperty =
DependencyProperty.RegisterAttached("IsActive",
typeof(bool),
typeof(StyleAnimation),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsActivePropertyChanged)));
public static bool GetIsActive(UIElement element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return (bool)element.GetValue(IsActiveProperty);
}
public static void SetIsActive(UIElement element, bool value)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
element.SetValue(IsActiveProperty, value);
}
static StyleAnimation()
{
// You can specify any owner type, derived from FrameworkElement.
// For example if you want to animate style for every Control derived
// class - use Control. If Label is your single target - set it to label.
// But be aware: value coercion will be called every time your style is
// updated. So if you have performance problems, probably you should
// narrow owner type to your exact type.
FrameworkElement.StyleProperty.AddOwner(typeof(Control),
new FrameworkPropertyMetadata(
null, new PropertyChangedCallback(StyleChanged), CoerceStyle));
}
private static object CoerceStyle(DependencyObject d, object baseValue)
{
var c = d as Control;
if (c == null || c.Style == baseValue)
{
return baseValue;
}
if (CheckAndUpdateAnimationStartedFlag(c))
{
return baseValue;
}
// If we get here, it means we have to start our animation loop:
// 1. Hide control with old style.
// 2. When done set control's style to new one. This will reenter to this
// function, but will return baseValue, since CheckAndUpdateAnimationStartedFlag()
// will be true.
// 3. Show control with new style.
var showAnimation = new DoubleAnimation
{
Duration =
new Duration(TimeSpan.FromMilliseconds(DURATION_MS)),
To = 1
};
var hideAnimation = new DoubleAnimation
{
Duration = new Duration(TimeSpan.FromMilliseconds(DURATION_MS)),
To = 0
};
hideAnimation.Completed += (o, e) =>
{
// To stress it one more: this will trigger value coercion again,
// but CheckAndUpdateAnimationStartedFlag() function will reture true
// this time, and we will not go to this loop again.
c.CoerceValue(FrameworkElement.StyleProperty);
c.BeginAnimation(UIElement.OpacityProperty, showAnimation);
};
c.BeginAnimation(UIElement.OpacityProperty, hideAnimation);
return c.Style; // Return old style this time.
}
private static void StyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// So what? Do nothing.
}
private static void OnIsActivePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var fe = d as FrameworkElement;
if (fe == null)
{
return;
}
if (GetIsActive(fe))
{
HookStyleChanges(fe);
}
else
{
UnHookStyleChanges(fe);
}
}
private static void UnHookStyleChanges(FrameworkElement fe)
{
if (_hookedElements.Contains(fe))
{
_hookedElements.Remove(fe);
}
}
private static void HookStyleChanges(FrameworkElement fe)
{
_hookedElements.Add(fe, false);
}
private static bool CheckAndUpdateAnimationStartedFlag(Control c)
{
var hookedElement = _hookedElements.Contains(c);
if (!hookedElement)
{
return true; // don't need to animate unhooked elements.
}
var animationStarted = (bool)_hookedElements[c];
_hookedElements[c] = !animationStarted;
return animationStarted;
}
}
public class BooleanStyleConverter : IValueConverter
{
public Style StyleFalse { get; set; }
public Style StyleTrue { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)value)
{
return StyleTrue;
}
return StyleFalse;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Cheers :)
I too got a solution becuase I as well liked your problem :)
I solved the puzzle using ColorAnimations on the gradient. Have a look:
<Window x:Class="WpfTest___App.DoEvents.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" MinHeight="300" MinWidth="300"
Name="Window">
<Grid DataContext="{Binding ElementName=Window}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="Some content here" Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Label.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="Blue" Offset="0"/>
<GradientStop Color="LightBlue" Offset="0.4"/>
<GradientStop Color="White" Offset="1"/>
</LinearGradientBrush>
</Label.Background>
<Label.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsCheckedOut}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.GradientStops[0].Color"
To="Red" Duration="0:0:5"/>
<ColorAnimation Storyboard.TargetProperty="Background.GradientStops[1].Color"
To="Orange" Duration="0:0:5"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.GradientStops[0].Color"
To="Blue" Duration="0:0:5"/>
<ColorAnimation Storyboard.TargetProperty="Background.GradientStops[1].Color"
To="LightBlue" Duration="0:0:5"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
<Button Content="Change IsCheckedOut" Click="Button_Click" Grid.Row="1"/>
</Grid>
In the code behind I created a Dependency property for testing and a listener to the Click event of the button:
public partial class Window1 : Window
{
public bool IsCheckedOut
{
get { return (bool)GetValue(IsCheckedOutProperty); }
set { SetValue(IsCheckedOutProperty, value); }
}
public static readonly DependencyProperty IsCheckedOutProperty = DependencyProperty.Register("IsCheckedOut", typeof(bool), typeof(Window1), new PropertyMetadata(false));
public Window1()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
IsCheckedOut = !IsCheckedOut;
}
}
This should solve your problem as well :)
Related
I'm working towards making click and drag-able spline curves while learning WPF. I've been able to successfully work with pure Line segments, but making the jump to a polyline is proving difficult. I have a class for interpolating the spline curves that I used to use in WinForms, so I'm using a few input clicks from the mouse, and those will be the thumbs to click and drag. The interpolated points have a high enough resolution that a WPF Polyline should be fine for display. To clarify, I need the higher resolution output, so using a WPF Beizer is not going to work.
I have the outline pretty well setup- but the particular issue I'm having, is that dragging the thumbs does not either a) the two way binding is not setup correctly, or b) the ObservableCollection is not generating notifications. I realize that the ObservableCollection only notifies when items are added/removed/cleared, etc, and not that the individual indices are able to produce notifications. I have spent the last few hours searching- found some promising ideas, but haven't been able to wire them up correctly. There was some code posted to try inherit from ObservableCollection and override the OnPropertyChanged method in the ObservableCollection, but that's a protected virtual method. While others used a method call into the OC to attach PropertyChanged event handlers to each object, but I'm unsure where to inject that logic. So I am a little stuck.
MainWindow.xaml:
There is an ItemsControl hosted in a mainCanvas. ItemsControl is bound to a property on the ViewModel
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Menu>
<MenuItem x:Name="menuAddNewPolyline" Header="Add Polyline" Click="MenuItem_Click" />
</Menu>
<Canvas x:Name="mainCanvas" Grid.Row="1">
<ItemsControl x:Name="polylinesItemsControl"
ItemsSource="{Binding polylines}"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</Grid>
MainWindow.Xaml.cs:
Pretty simple- initializes a new view model, and it's set as the DataContext. There is a menu with a Add Polyline item, which in turn, initializes a new PolylineControl, and generates three random points (using Thread.Sleep, otherwise they were the same, between the calls) within the ActualHeight and ActualWidth of the window. The new PolylineControl is added to the ViewModel in an ObservableCollection This is a stand in until I get to accepting mouse input.
public partial class MainWindow : Window
{
private ViewModel viewModel;
public MainWindow()
{
InitializeComponent();
viewModel = new ViewModel();
DataContext = viewModel;
}
private Point GetRandomPoint()
{
Random r = new Random();
return new Point(r.Next(0, (int)this.ActualWidth), r.Next(0, (int)this.ActualHeight));
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
var newPolyline = new PolylineControl.Polyline();
newPolyline.PolylinePoints.Add(GetRandomPoint());
Thread.Sleep(100);
newPolyline.PolylinePoints.Add(GetRandomPoint());
Thread.Sleep(100);
newPolyline.PolylinePoints.Add(GetRandomPoint());
viewModel.polylines.Add(newPolyline);
}
}
ViewModel.cs:
Absolutely noting fancy here
public class ViewModel
{
public ObservableCollection<PolylineControl.Polyline> polylines { get; set; }
public ViewModel()
{
polylines = new ObservableCollection<PolylineControl.Polyline>();
}
}
**The PolylineControl:
Polyline.cs:**
Contains DP's for an ObservableCollection of points for the polyline. Eventually this will also contain the interpolated points as well as the input points, but a single collection of points will do for the demo. I did try to use the INotifyPropertyChanged interface to no avail.
public class Polyline : Control
{
public static readonly DependencyProperty PolylinePointsProperty =
DependencyProperty.Register("PolylinePoints", typeof(ObservableCollection<Point>), typeof(Polyline),
new FrameworkPropertyMetadata(new ObservableCollection<Point>()));
public ObservableCollection<Point> PolylinePoints
{
get { return (ObservableCollection<Point>)GetValue(PolylinePointsProperty); }
set { SetValue(PolylinePointsProperty, value); }
}
static Polyline()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Polyline), new FrameworkPropertyMetadata(typeof(Polyline)));
}
}
Generic.xaml
Contains a canvas with a databound Polyline, and an ItemsControl with a DataTemplate for the ThumbPoint control.
<local:PointCollectionConverter x:Key="PointsConverter"/>
<Style TargetType="{x:Type local:Polyline}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Polyline}">
<Canvas Background="Transparent">
<Polyline x:Name="PART_Polyline"
Stroke="Black"
StrokeThickness="2"
Points="{Binding Path=PolylinePoints,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource PointsConverter}}"
>
</Polyline>
<ItemsControl x:Name="thumbPoints"
ItemsSource="{Binding PolylinePoints, RelativeSource={RelativeSource TemplatedParent}}"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<tc:ThumbPoint Point="{Binding Path=., Mode=TwoWay}"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
PointsCollectionConverter.cs:
Contains a IValueConverter to turn the ObservableCollection into a PointsCollection.
public class PointCollectionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.GetType() == typeof(ObservableCollection<Point>) && targetType == typeof(PointCollection))
{
var pointCollection = new PointCollection();
foreach (var point in value as ObservableCollection<Point>)
{
pointCollection.Add(point);
}
return pointCollection;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
And finally, the ThumbPointControl:
ThumbPoint.cs:
Contains a single DP for the center of the point, along with the DragDelta functionality.
public class ThumbPoint : Thumb
{
public static readonly DependencyProperty PointProperty =
DependencyProperty.Register("Point", typeof(Point), typeof(ThumbPoint),
new FrameworkPropertyMetadata(new Point()));
public Point Point
{
get { return (Point)GetValue(PointProperty); }
set { SetValue(PointProperty, value); }
}
static ThumbPoint()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ThumbPoint), new FrameworkPropertyMetadata(typeof(ThumbPoint)));
}
public ThumbPoint()
{
this.DragDelta += new DragDeltaEventHandler(this.OnDragDelta);
}
private void OnDragDelta(object sender, DragDeltaEventArgs e)
{
this.Point = new Point(this.Point.X + e.HorizontalChange, this.Point.Y + e.VerticalChange);
}
}
Generic.xaml:
Contains the style, and an Ellipse bound which is databound.
<Style TargetType="{x:Type local:ThumbPoint}">
<Setter Property="Width" Value="8"/>
<Setter Property="Height" Value="8"/>
<Setter Property="Margin" Value="-4"/>
<Setter Property="Background" Value="Gray" />
<Setter Property="Canvas.Left" Value="{Binding Path=Point.X, RelativeSource={RelativeSource Self}}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Point.Y, RelativeSource={RelativeSource Self}}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ThumbPoint}">
<Ellipse x:Name="PART_Ellipse"
Fill="{TemplateBinding Background}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Window after the Add Polyline menu item is pressed
The code works to add the polyline with three random points.
Thumbs moved away from poly line
However, once you move the thumbs, the polyline does not update along with it.
I have a working example of just a single line segment (added to the view model as many times as you click the add segment button) so it seems the logic should all be correct, but something broke down with the introduction of the ObservableCollection to host the multiple points required for a polyline.
Any help is appreciated
Following on from Clemens suggestions, I was able to make it work.
I renamed the Polyline.cs control to eliminate confusion with the standard WPF Polyline Shape class to DynamicPolyline. The class now implements INotifyPropertyChanged, and has DP for the PolylinePoints and a seperate ObservableCollection for a NotifyingPoint class which also implements INotifyPropertyChanged. When DynamicPolyline is initialized, it hooks the CollectionChanged event on the ObserableCollection. The event handler method then either adds an event handler to each item in the collection, or removes it based on the action. The event handler for each item simply calls SetPolyline, which in turn cycles through the InputPoints adding them to a new PointCollection, and then sets the Points property on the PART_Polyline (which a reference to is created in the OnApplyTemplate method).
It turns out the Points property on a Polyline does not listen to the INotifyPropertyChanged interface, so data binding in the Xaml was not possible. Probably will end up using a PathGeometery in the future, but for now, this works.
To address Marks non MVVM concerns.. It's a demo app, sorry I had some code to test things in the code behind. The point is to be able to reuse these controls, and group them with others for various use cases, so it makes more sense for them to be on their own vs repeating the code.
DynmicPolyline.cs:
public class DynamicPolyline : Control, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string caller = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
}
public static readonly DependencyProperty PolylinePointsProperty =
DependencyProperty.Register("PoilylinePoints", typeof(PointCollection), typeof(DynamicPolyline),
new PropertyMetadata(new PointCollection()));
public PointCollection PolylinePoints
{
get { return (PointCollection)GetValue(PolylinePointsProperty); }
set { SetValue(PolylinePointsProperty, value); }
}
private ObservableCollection<NotifyingPoint> _inputPoints;
public ObservableCollection<NotifyingPoint> InputPoints
{
get { return _inputPoints; }
set
{
_inputPoints = value;
OnPropertyChanged();
}
}
private void SetPolyline()
{
if (polyLine != null && InputPoints.Count >= 2)
{
var newCollection = new PointCollection();
foreach (var point in InputPoints)
{
newCollection.Add(new Point(point.X, point.Y));
}
polyLine.Points = newCollection;
}
}
private void InputPoints_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (var item in e.NewItems)
{
var point = item as NotifyingPoint;
point.PropertyChanged += InputPoints_PropertyChange;
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (var item in e.OldItems)
{
var point = item as NotifyingPoint;
point.PropertyChanged -= InputPoints_PropertyChange;
}
}
}
private void InputPoints_PropertyChange(object sender, PropertyChangedEventArgs e)
{
SetPolyline();
}
public DynamicPolyline()
{
InputPoints = new ObservableCollection<NotifyingPoint>();
InputPoints.CollectionChanged += InputPoints_CollectionChanged;
SetPolyline();
}
static DynamicPolyline()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DynamicPolyline), new FrameworkPropertyMetadata(typeof(DynamicPolyline)));
}
private Polyline polyLine;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
polyLine = this.Template.FindName("PART_Polyline", this) as Polyline;
}
NotifyingPoint.cs
Simple class that raises property changed events when X or Y is updated from the databound ThumbPoint.
public class NotifyingPoint : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string caller = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
}
public event EventHandler ValueChanged;
private double _x = 0.0;
public double X
{
get { return _x; }
set
{
_x = value;
OnPropertyChanged();
ValueChanged?.Invoke(this, null);
}
}
private double _y = 0.0;
public double Y
{
get { return _y; }
set
{
_y = value;
OnPropertyChanged();
}
}
public NotifyingPoint()
{
}
public NotifyingPoint(double x, double y)
{
X = x;
Y = y;
}
public Point ToPoint()
{
return new Point(_x, _y);
}
}
And finally, for completeness, here is the Generic.xaml for the control. Only change in here was the bindings for X and Y of the NotifyingPoint.
<Style TargetType="{x:Type local:DynamicPolyline}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:DynamicPolyline}">
<Canvas x:Name="PART_Canvas">
<Polyline x:Name="PART_Polyline"
Stroke="Black"
StrokeThickness="2"
/>
<ItemsControl x:Name="PART_ThumbPointItemsControl"
ItemsSource="{Binding Path=InputPoints, RelativeSource={RelativeSource TemplatedParent}}"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<tc:ThumbPoint X="{Binding Path=X, Mode=TwoWay}" Y="{Binding Path=Y, Mode=TwoWay}"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I dropped my Spline class in to the SetPolyline method, and got the result I was after:
Two working click and drag able spline curves
Universal windows apps don't support data triggers.
Without data triggers, how can I change the background color of a button using xaml and data binding only when a boolean property in the view model changes?
For example given this XAML:
<StackPanel>
<Button Name="ButtonA" Click="ButtonA_Click" Content="A" />
<Button Name="ButtonB" Click="ButtonB_Click" Content="B" />
<Button Name="ButtonC" Click="ButtonC_Click" Content="C" />
</StackPanel>
with this code behind
private void ButtonA_Click(object sender, RoutedEventArgs e)
{
Model.IsOnA = !Model.IsOnA;
}
private void ButtonB_Click(object sender, RoutedEventArgs e)
{
Model.IsOnB = !Model.IsOnB;
}
private void ButtonC_Click(object sender, RoutedEventArgs e)
{
Model.IsOnC = !Model.IsOnC;
}
What is the best approach to change the background color of the buttons using data binding when the corresponding property in the view model is changed?
I was able to make it work for one button only using the VisualStateManager manager:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState>
<VisualState.StateTriggers>
<StateTrigger IsActive="{x:Bind Model.IsOnA, Mode=OneWay}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="ButtonA.Background" Value="Red"></Setter>
<Setter Target="ButtonA.Foreground" Value="White"></Setter>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
But with multiple buttons that bind to different properties in the view model this approach is not working.
You can check my previous answer in the following link. Delete button on ListView items
You just need to create a converter which converts Boolean to SolidColorBrush. For example:
public class BooleanToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return (value is bool && (bool)value) ? new SolidColorBrush(Colors.Red) : new SolidColorBrush(Colors.Green);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new Exception("Not Implemented");
}
}
And to add it to your Xaml Binding.
<Page.Resources>
<local:BooleanToColorConverter x:Key="ColorConverter"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{x:Bind Activities}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Activity">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="txt" Text="{x:Bind Name}" Grid.Column="0"/>
<Button x:Name="delItem" Click="delItem_Click" Grid.Column="1" Foreground="{x:Bind Visible, Mode=OneWay, Converter={StaticResource ColorConverter}}" Background="Transparent" Margin="100, 0, 0, 0">
<SymbolIcon Symbol="Delete"/>
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
An alternative to converters are attached properties. Useful if more than one property should be changed or if one needs access to the view model (through the DataContext of the control) to decide how to change the user interface properties.
Here is a simple example:
public class IsFavoriteBehavior
{
public static bool GetIsFavorite(DependencyObject obj)
{
return (bool)obj.GetValue(IsFavoriteProperty);
}
public static void SetIsFavorite(DependencyObject obj, bool value)
{
obj.SetValue(IsFavoriteProperty, value);
}
public static readonly DependencyProperty IsFavoriteProperty =
DependencyProperty.RegisterAttached("IsFavorite", typeof(bool), typeof(Button), new PropertyMetadata(false, (o, e) =>
{
var button = o as Button;
if (button == null)
return;
if ((bool)e.NewValue)
{
button.Background = (SolidColorBrush)Application.Current.Resources["HighlightBackgroundColorBrush"];
button.Foreground = (SolidColorBrush)Application.Current.Resources["HighlightTextColorBrush"];
}
else
{
button.Background = (SolidColorBrush)Application.Current.Resources["NormalBackgroundColorBrush"];
button.Foreground = (SolidColorBrush)Application.Current.Resources["NormalTextColorBrush"];
}
o.SetValue(IsFavoriteProperty, e.NewValue);
}));
}
It can be used like this in XAML:
<Button Name="FavoriteButton" Content="Favorite" local:IsFavoriteBehavior.IsFavorite="{x:Bind ViewModel.Favorite, Mode=OneWay}" >
One could put a property with the background color brush directly into the view model.
For example in the view model:
SolidColorBrush IsOnABackground
{
get
{
if(IsOnA)
return (SolidColorBrush)Application.Current.Resources["HighlightBackgroundColorBrush"];
else
return (SolidColorBrush)Application.Current.Resources["NormalBackgroundColorBrush"];
}
}
bool isOnA = false;
bool IsOnA
{
set
{
if (isOnA != value)
{
isOnA = value;
OnPropertyChanged("IsOnA");
OnPropertyChanged("IsOnABackground");
}
}
get { return isOnA; }
}
and in XAML:
<Button Name="ButtonA" Content="A" Background="{x:Bind ViewModel.IsOnABackground, Mode=OneWay}" />
I have the following INotifyDataErrorInfo implementation in an abstract base class.
private IEnumerable<ValidationErrorModel> _validationErrors = new List<ValidationErrorModel>();
public IEnumerable<ValidationErrorModel> ValidationErrors
{
get { return _validationErrors; }
private set
{
_validationErrors = value;
OnPropertyChanged();
}
}
protected abstract Task<ValidationResult> GetValidationResultAsync();
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName) ||
ValidationErrors == null)
return null;
IEnumerable<string> errors = ValidationErrors
.Where(p => p.PropertyName.Equals(propertyName))
.Select(p => p.ToString())
.ToList();
return errors;
}
public bool HasErrors
{
get
{
bool hasErrors = ValidationErrors != null && ValidationErrors.Any();
return hasErrors;
}
}
public Task<ValidationResult> ValidateAsync()
{
Task<ValidationResult> validationResultTask = GetValidationResultAsync();
validationResultTask.ContinueWith((antecedent) =>
{
if (antecedent.IsCompleted &&
!antecedent.IsCanceled &&
!antecedent.IsFaulted)
{
ValidationResult validationResult = antecedent.Result;
if (validationResult != null)
{
lock (ValidationErrors)
{
ValidationErrors =
validationResult.Errors
.Select(validationFailure =>
new ValidationErrorModel(validationFailure.PropertyName, validationFailure.ErrorMessage))
.ToList();
foreach (ValidationErrorModel validationErrorModel in ValidationErrors)
{
RaiseErrorsChanged(validationErrorModel.PropertyName);
}
}
}
}
});
return validationResultTask;
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged = delegate { };
protected virtual void RaiseErrorsChanged(string propertyName)
{
var handler = ErrorsChanged;
if (handler != null)
{
Dispatcher.InvokeOnMainThread(() =>
{
handler(this, new DataErrorsChangedEventArgs(propertyName));
});
}
}
In models deriving from the base class I implement the Task<ValidationResult> GetValidationResultAsync() required method, it uses fluent validation Nuget package.
private readonly ModelValidator _modelValidator = new ModelValidator();
protected override Task<ValidationResult> GetValidationResultAsync()
{
return _modelValidator.ValidateAsync(this);
}
The problem is that when I invoke from a ViewModel the ValidateAsync() method of a model the UI input controls are not invalidate/validate correctly, I actually have a tab control and validate the models in tab index changed, some might show the red border once I change tab but then again return to normal state to the next tab change.
In debug it shows that the ValidationErrors property returns errors.
My XAML input controls code is like below.
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:"/>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnNotifyDataErrors=True}" Width="200"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Scheduled Date:"/>
<DatePicker DisplayDate="{Binding ScheduledDate, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnNotifyDataErrors=True}"/>
</StackPanel>
</StackPanel>
</Grid>
[Update 1]
I should mention that I use in the MainWindow a tab control and 3 tab items, each tab item is a UserControl.
I hooked up to the Validation.Error event of all the XAML UserControls and I noticed that even I get tab selected index changed value the Validation.Error fires once for the first tab and never again, I suspect there is a cleanup somewhere for a reason.
Code for the SelectedTabIndex that fires the models validations.
private int _selectedTabIndex = 0;
public int SelectedTabIndex
{
get { return _selectedTabIndex; }
set
{
_selectedTabIndex = value;
ValidateModels();
Tab2ViewModel.ValidateModels();
Tab3ViewModel.ValidateModels();
OnPropertyChanged();
}
}
The ValidateModels method calls ValidateAsync of the model in the ViewModel.
public override Task ValidateModels()
{
return Model.ValidateAsync();
}
MainWindow TabControl XAML.
<TabControl SelectedIndex="{Binding SelectedTabIndex, Mode=TwoWay}">
[Update 2]
After adding a custom error style and a custom error template, I see that the controls tooltip stay with the condition not met error but the error template is clearing. So, the TextBox shows no error template, custom or default, but the validation error exists and the tooltip shows the error.
Why the XAML templates clear on TabIndexChange and how come they don't refresh at least on the active tab item I'm viewing. This might be the problem that I should solve.
Also, as mentioned before, I don't see the ErrorsChanged revalidating the controls except the first time the SelectedTabIndex setter is invoked.
The templates I added.
<Application.Resources>
<Style x:Key="ErrorStyle"
TargetType="FrameworkElement">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding (Validation.Errors).CurrentItem.ErrorContent, RelativeSource={x:Static RelativeSource.Self}}"></Setter>
</Trigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="TextBoxErrorTemplate">
<DockPanel>
<Ellipse DockPanel.Dock="Right"
Margin="2,0"
ToolTip="Contains Invalid Data"
Width="10"
Height="10"
>
<Ellipse.Fill>
<LinearGradientBrush>
<GradientStop Color="#11FF1111" Offset="0"/>
<GradientStop Color="#FFFF0000" Offset="1"/>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="4,4,15,4"/>
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource TextBoxErrorTemplate}"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip">
<Setter.Value>
<Binding Path="(Validation.Errors).CurrentItem.ErrorContent" RelativeSource="{x:Static RelativeSource.Self}"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Application.Resources>
The problem is that tabs, expanders etc don't work well with validators, you need to include AdornerDecorator, or not use tabs which in my case is not an option.
Issue with WPF validation(IDataErrorInfo) and tab focusing.
I have ContentPresenter with DataTemplateSelector:
...
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var model = item as ItemControlViewModel;
if (model.CurrentStatus == PrerequisitesStatus.Required)
{
return RequiredTemplate;
}
if (model.CurrentStatus == PrerequisitesStatus.Completed)
{
return FinishedTemplate;
}
...
return InProgressTemplate;
}
When CurrentStatus is changed, OnPropertyChanged is called.
I need somehow to trigger this DataTemplateSelector when the property is changed and change ContentPresenter DataTemplate. Any suggestions?
Threre are similar questions:
1
2, but I don't want to use any DataTriggers, because of too much states.
Tried to play with DataTriggers
<ContentPresenter
Grid.Column="1"
Height="16"
Width="16"
Margin="3">
<ContentPresenter.Triggers>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="0">
<Setter Property="ContentPresenter.ContentTemplate" Value="{StaticResource ResourceKey=_requiredStatusTemplate}" />
</DataTrigger>
</ContentPresenter.Triggers>
</ContentPresenter>
But got an error:
Triggers collection members must be of type EventTrigger :(
As you requested an example with datatriggers in the comments, here you are:
A FrameworkElement can only have EventTriggers, therefore you get the error Message Triggers collection members must be of type EventTrigger
And also don't use a ContentPresenter directly, it is meant to be used inside a ControlTemplate. Better use a ContentControl when you want to have dynamic content.
See What's the difference between ContentControl and ContentPresenter?
And finally here's a suggestion to your DataTrigger issue. I have put it inside a style for reusability ....
XAML :
<Window x:Class="WpfApplication88.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">
<Window.Resources>
<DataTemplate x:Key="requiredTemplate">
<TextBlock Text="requiredTemplate"></TextBlock>
<!--your stuff here-->
</DataTemplate>
<DataTemplate x:Key="completedTemplate">
<TextBlock Text="CompletedTemplate"></TextBlock>
<!--your stuff here-->
</DataTemplate>
<Style x:Key="selectableContentStyle" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="Required">
<Setter Property="ContentTemplate" Value="{StaticResource requiredTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="Completed">
<Setter Property="ContentTemplate" Value="{StaticResource completedTemplate}" />
</DataTrigger>
<!-- your other Status' here -->
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ContentControl Width="100" Height="100" Style="{StaticResource selectableContentStyle}"/>
</Grid>
</Window>
I could be wrong, but I believe the DataTemplateSelector is only used when the ItemContainerGenerator creates a container for an item added to the collection. Because a new container isn't generated when a property value changes, a new DataTemplate is never going to be applied via the selector.
As suggested in the comments, I would recommend you look at the VisualStateManager or data triggers, otherwise you're going to have to recreate the container for every item when one or more properties change value.
Just as an extra choice - if you want to stick to your templates, just use s binding with converter.
I came up with a behavior that would theoretically do this.
C#:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
public class UpdateTemplateBehavior : Behavior<ContentPresenter>
{
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(UpdateTemplateBehavior), new FrameworkPropertyMetadata(null, OnContentChanged));
public object Content
{
get => GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
static void OnContentChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is UpdateTemplateBehavior behavior)
behavior.Update();
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(object), typeof(UpdateTemplateBehavior), new FrameworkPropertyMetadata(null, OnValueChanged));
public object Value
{
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is UpdateTemplateBehavior behavior)
behavior.Update();
}
public UpdateTemplateBehavior() : base() { }
protected override void OnAttached()
{
base.OnAttached();
Update();
}
void Update()
{
if (Content != null)
{
BindingOperations.ClearBinding(AssociatedObject, ContentPresenter.ContentProperty);
AssociatedObject.Content = null;
BindingOperations.SetBinding(AssociatedObject, ContentPresenter.ContentProperty, new Binding() { Path = nameof(Content), Source = this });
}
}
}
XAML:
<ContentPresenter ContentTemplateSelector="{StaticResource MySelector}">
<i:Interaction.Behaviors>
<Behavior:UpdateTemplateBehavior Content="{Binding SomeContent}"
Value="{Binding SomeValue}"/>
</i:Interaction.Behaviors>
</ContentPresenter>
The content is "updated" (by clearing and then resetting the binding) when the content (in this example, "SomeContent") and an arbitrary value (in this example, "SomeValue") is changed, as well as when the behavior is first attached.
An update is not made unless the content is not null (my project-specific requirement). Not updating upon attaching may avoid unintentionally updating twice at once, but if the value is initially null, an update wouldn't occur until the value changes at least once.
Note: In the above example, I am not sure if the behavior has the same data context as the ContentPresenter. I use a helper class that I did not include here for brevity. Keep that in mind when testing...
Is there a marker bar component for a C# application what i could use in my application? As marker bar i mean something like ReSharper adds to Visual Studio:
Another example for something similar (the bar on the left):
EDIT: I found non-free component for java http://www.sideofsoftware.com/marker_bar/doc/sos/marker/JMarkerBar.html what does exactly what i want to do. It doesnt suite for me but maybe it helps someone.
In WPF the bar is a bit like a ListBox with just a different way of displaying a 1 pixel high line for each line of text. The state of the line would influence the color of the line and selecting a line would raise the SelectionChanged event to which the textbox could respond.
Let me know if you want me to show a prototype.
EDIT
Here goes. You can click/select a line in the bar and the textbox will scroll to that line.
Still to add:
what to do when the number of lines is to large for the bar?
A different way to show the line that is current in the bar?
Keep the selected line in the bar in sync with the caret in the text box.
...
These can be solved but depend largely on what you want. This should get you started.
XAML:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow"
Height="350"
Width="525">
<Window.Resources>
<local:StatusToBrushConverter x:Key="statusToBrushConverter" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding}"
SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
<Setter Property="Opacity"
Value="0.5" />
<Setter Property="MaxHeight"
Value="1" />
<Setter Property="MinHeight"
Value="1" />
<Style.Triggers>
<Trigger Property="IsSelected"
Value="True">
<Trigger.Setters>
<Setter Property="Opacity"
Value="1.0" />
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Rectangle StrokeThickness="0"
Stroke="Green"
Fill="{Binding Status, Converter={StaticResource statusToBrushConverter}}"
Height="1"
HorizontalAlignment="Stretch" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox AcceptsReturn="True"
Grid.Column="1"
x:Name="codeBox" />
</Grid>
</Window>
C#:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication2
{
public partial class MainWindow : Window
{
private CodeLines lines;
public MainWindow()
{
InitializeComponent();
lines = new CodeLines();
Random random = new Random();
for (int i = 0; i < 200; i++)
{
lines.Add(new CodeLine { Status = (VersionStatus)random.Next(0, 5), Line = "Line " + i });
}
this.DataContext = lines;
codeBox.Text = String.Join("\n", from line in lines
select line.Line);
}
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selectedLine = ((ListBox)sender).SelectedIndex;
codeBox.ScrollToLine(selectedLine);
}
}
public enum VersionStatus
{
Original,
Added,
Modified,
Deleted
}
public class CodeLine : INotifyPropertyChanged
{
private VersionStatus status;
public VersionStatus Status
{
get { return status; }
set
{
if (status != value)
{
status = value;
OnPropertyChanged("Status");
}
}
}
private string line;
public string Line
{
get { return line; }
set
{
if (line != value)
{
line = value;
OnPropertyChanged("Line");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var p = PropertyChanged;
if (p != null)
{
p(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class CodeLines : ObservableCollection<CodeLine>
{
}
class StatusToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var status = (VersionStatus)value;
switch (status)
{
case VersionStatus.Original:
return Brushes.Green;
case VersionStatus.Added:
return Brushes.Blue;
case VersionStatus.Modified:
return Brushes.Yellow;
case VersionStatus.Deleted:
return Brushes.Red;
default:
return DependencyProperty.UnsetValue;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
You could use the Graphics class on a panel to paint it yourself.
http://msdn.microsoft.com/en-us/library/system.drawing.graphics.aspx
(I wouldn't use a bar graph as Teoman Soygul suggested, that's abusing components for something they aren't supposed to do. Same with the listbox idea by Erno. List boxes are made for showing lines of text, not marker bars.)