I have a WPF data trigger that is set to fire when a value is true.
I want this trigger to fire everytime this value is set to true, even if it was true before. Unfortunately it seems only to fire if the value is changed from true to false or vise versa. My underlying data model is firing the PropertyChanged event of INotifyPropertyChanged even if the value is set to true twice in succession but the Trigger doesn't seem to pick this up.
Is there anyway to make the trigger run regardless of whether the bound value has changed?
Interesting to note that converters will be called each time. The problem is more specific to running an animation.
If I change my code to reset the value to false and then back to true again it does fire the animation. Obviously this is not ideal and doesn't make the code nice to read. I'm hoping there is a better way to do this.
Any help greatly appreciated.
WPF code
<Grid>
<Grid.Resources>
<Storyboard x:Key="AnimateCellBlue">
<ColorAnimation Storyboard.TargetProperty="Background.Color" From="Transparent" To="Blue" Duration="0:0:0.1" AutoReverse="True" RepeatBehavior="1x" />
</Storyboard>
</Grid.Resources>
<TextBox Name="txtBox" Text="{Binding DataContext.DisplayText, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.IsTrue, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="BidSizeUpStoryB" Storyboard="{StaticResource AnimateCellBlue}" />
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</Grid>
Code Behind:-
public partial class MainWindow : Window
{
private DataItem _dataItem;
private DispatcherTimer _dispatcherTimer;
public MainWindow()
{
InitializeComponent();
_dataItem = new DataItem();
_dataItem.DisplayText = "Testing";
_dataItem.IsTrue = true;
this.DataContext = _dataItem;
_dispatcherTimer = new DispatcherTimer(TimeSpan.FromSeconds(1), DispatcherPriority.Normal, TimerCallbackHandler, Dispatcher);
}
private void TimerCallbackHandler(object s, EventArgs args)
{
Console.WriteLine("In Timer");
_dataItem.IsTrue = true;
_dataItem.DisplayText = "Timer " + DateTime.Now.Ticks;
}
}
DataItem:-
public class DataItem : INotifyPropertyChanged
{
private bool _isTrue;
private string _displayText;
public bool IsTrue
{
get { return _isTrue; }
set
{
_isTrue = value;
NotifyPropertyChanged("IsTrue");
}
}
public string DisplayText
{
get
{
return _displayText;
}
set
{
_displayText = value;
NotifyPropertyChanged("DisplayText");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
Related
I'm new in WPF and i'm exploring listbox control.
I created a listbox, items represent image plus text.
Xaml code:
<ListBox x:Name="LstB_Checklist" HorizontalAlignment="Left" Height="139" Margin="48,61,0,0" VerticalAlignment="Top" Width="220">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image>
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Checked}" Value="false">
<Setter Property="Source" Value="pack://application:,,,/listbox;component/Pictures/BulletOff.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding Checked}" Value="true">
<Setter Property="Source" Value="pack://application:,,,/listbox;component/Pictures/BulletOn.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
the binding allows to properly set items and image at startup.
Code:
public MainWindow()
{
InitializeComponent();
List<LstB_Item> items = new List<LstB_Item>();
items.Add(new LstB_Item() { Title = "Item1", Checked = "false" });
items.Add(new LstB_Item() { Title = "Item2", Checked = "false" });
LstB_Checklist.ItemsSource = items;
}
public class LstB_Item
{
public string Title { get; set; }
public string Checked { get; set; }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//
}
I would like to know how to change the image according to some conditions, when i clik on a button (e.g selected item image turn to "bulletOn" instead of "bulltOff" according to external condition, not based on "onselect" trigger)
Many thanks
As Clemens suggests the class should implement the INotifyPropertyChanged interface and raise change notifications whenever the Checked property is set to a new value:
public class LstB_Item : INotifyPropertyChanged
{
public string Title { get; set; }
private string _checked;
public string Checked
{
get { return _checked; }
set { _checked = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You should then be able to simply change the value of the Checked property for the item that you want to change the image for:
private void Button_Click(object sender, RoutedEventArgs e)
{
List<LstB_Item> items = LstB_Checklist.ItemsSource as List<LstB_Item>;
items[0].Checked = "true";
}
Please refer to MSDN for more information about the INotifyPropertyChanged interface: https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged(v=vs.110).aspx
You should also consider changing the type of your Checked property from string to bool:
private bool _checked;
public bool Checked
{
get { return _checked; }
set { _checked = value; NotifyPropertyChanged(); }
}
I have two kinds of menu items according to the login. So, by using the property in the ViewModel Class
bool IsAdmin {get; set;}
I have to change the menu item content.I am not familiar with data template. I want to define all the menu items in the xaml itself (might be using the data templates).
How we can bind the differnt menu items by using the data trigger.
Can anyone can give a smaller example for this. using only this property and no c# codes.
Use ContentControl and Styles for max flexability in changing the view betwin Admin or not Admin views
<UserControl.Resources>
<!--*********** Control templates ***********-->
<ControlTemplate x:Key="ViewA">
<Views:AView/>
</ControlTemplate>
<ControlTemplate x:Key="ViewB">
<Views:BView />
</ControlTemplate>
</UserControl.Resources>
<Grid>
<ContentControl DataContext="{Binding}" Grid.Row="1">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Template" Value="{StaticResource ViewA}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsAdmin}" Value="True">
<Setter Property="Template" Value="{StaticResource ViewB}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl >
</Grid>
Please keep in mind that you will have to implement the INPC interface on your VM in order to be able to change the state (is admin or not) on the fly.If not the change will be accepted only once(on the creation of the class that is holding the IsAdmin property). Here is the INPC implementation example:
public class UserControlDataContext:BaseObservableObject
{
private bool _isAdmin;
public bool IsAdmin
{
get { return _isAdmin; }
set
{
_isAdmin = value;
OnPropertyChanged();
}
}
}
/// <summary>
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
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;
}
}
I have a DataGrid that has its data refreshed by a background process every 15 seconds. If any of the data changes, I want to run an animation that highlights the cell with the changed value in yellow and then fade back to white. I sort-of have it working by doing the following:
I created a style with event trigger on Binding.TargetUpdated
<Style x:Key="ChangedCellStyle" TargetType="DataGridCell">
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Duration="00:00:15"
Storyboard.TargetProperty=
"(DataGridCell.Background).(SolidColorBrush.Color)"
From="Yellow" To="Transparent" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
And then applied it to the columns I wanted to highlight if a value changes
<DataGridTextColumn Header="Status"
Binding="{Binding Path=Status, NotifyOnTargetUpdated=True}"
CellStyle="{StaticResource ChangedCellStyle}" />
If the value for the status field in the database changes, the cell highlights in yellow just like I want. But, there are a few problems.
First, when the data grid is initially loaded, the entire column is highlighted in yellow. This makes sense, because all of the values are being loaded for the first time so you would expect TargetUpdated to fire. I'm sure there is some way I can stop this, but it's a relatively minor point.
The real problem is the entire column is highlighted in yellow if the grid is sorted or filtered in any way. I guess I don't understand why a sort would cause TargetUpdated to fire since the data didn't change, just the way it is displayed.
So my question is (1) how can I stop this behavior on initial load and sort/filter, and (2) am I on the right track and is this even a good way to do this? I should mention this is MVVM.
Since TargetUpdated is truly only UI update based event. It doesn't matter how update is happening. While sorting all the DataGridCells remain at their places only data is changed in them according to sorting result hence TargetUpdatedis raised. hence we have to be dependent on data layer of WPF app. To achieve this I've reset the Binding of DataGridCell based on a variable that kind of trace if update is happening at data layer.
XAML:
<Window.Resources>
<Style x:Key="ChangedCellStyle" TargetType="DataGridCell">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridCell">
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Duration="00:00:04" Storyboard.TargetName="myTxt"
Storyboard.TargetProperty="(DataGridCell.Background).(SolidColorBrush.Color)"
From="Red" To="Transparent" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ControlTemplate.Triggers>
<TextBox HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent"
Name="myTxt" >
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=DataContext.SourceUpdating}" Value="True">
<Setter Property="Text" Value="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=Content.Text,NotifyOnSourceUpdated=True,NotifyOnTargetUpdated=True}" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=DataContext.SourceUpdating}" Value="False">
<Setter Property="Text" Value="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=Content.Text}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel Orientation="Vertical">
<DataGrid ItemsSource="{Binding list}" CellStyle="{StaticResource ChangedCellStyle}" AutoGenerateColumns="False"
Name="myGrid" >
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="ID" Binding="{Binding Id}" />
</DataGrid.Columns>
</DataGrid>
<Button Content="Change Values" Click="Button_Click" />
</StackPanel>
Code Behind(DataContext object of Window):
public MainWindow()
{
list = new ObservableCollection<MyClass>();
list.Add(new MyClass() { Id = 1, Name = "aa" });
list.Add(new MyClass() { Id = 2, Name = "bb" });
list.Add(new MyClass() { Id = 3, Name = "cc" });
list.Add(new MyClass() { Id = 4, Name = "dd" });
list.Add(new MyClass() { Id = 5, Name = "ee" });
list.Add(new MyClass() { Id = 6, Name = "ff" });
InitializeComponent();
}
private ObservableCollection<MyClass> _list;
public ObservableCollection<MyClass> list
{
get{ return _list; }
set{
_list = value;
updateProperty("list");
}
}
Random r = new Random(0);
private void Button_Click(object sender, RoutedEventArgs e)
{
int id = (int)r.Next(6);
list[id].Id += 1;
int name = (int)r.Next(6);
list[name].Name = "update " + r.Next(20000);
}
Model Class: SourceUpdating property is set to true(which set the binding to notify TargetUpdate via a DataTrigger) when any notification is in progress for MyClass in updateProperty() method and after update is notified to UI, SourceUpdating is set to false(which then reset the binding to not notify TargetUpdate via a DataTrigger).
public class MyClass : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set {
name = value;updateProperty("Name");
}
}
private int id;
public int Id
{
get { return id; }
set
{
id = value;updateProperty("Id");
}
}
//the vaiable must set to ture when update in this calss is ion progress
private bool sourceUpdating;
public bool SourceUpdating
{
get { return sourceUpdating; }
set
{
sourceUpdating = value;updateProperty("SourceUpdating");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void updateProperty(string name)
{
if (name == "SourceUpdating")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
else
{
SourceUpdating = true;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
SourceUpdating = false;
}
}
}
Outputs:
Two simultaneous Updates/ Button is clicked once :
Many simultaneous Updates/ Button is clicked many times :
SO after update, when sorting or filtering is happening the bindings know that it doesn't have to invoke the TargetUpdated
event. Only when the update of source collection is in progress the
binding is reset to invoke the TargetUpdated event. Also initial coloring problem is also get handled by this.
However as the logic still has some sort comings as for editor TextBox the logic is based on with more complexity of data types and UI logic the code will become more complex also for initial binding reset whole row is animated as TargetUpdated is raised for all cells of a row.
My ideas for point (1) would be to handle this in the code. One way would be to handle the TargetUpdated event for the DataGridTextColumn and do an extra check on the old value vs. the new value, and apply the style only if the values are different, and perhaps another way would be to create and remove the binding programmatically based on different events in your code (like initial load, refresh, etc).
I suggest to use OnPropertyChanged for every props in your viewmodel and update related UIElement (start animation or whatever), so your problem will solved (on load, sort, filter,...) and also users can saw which cell changed!
I am trying to have the mediatimeline bind to a Uri like so:
<UserControl.Resources>
<Storyboard x:Key="myStoryboard">
<MediaTimeline Storyboard.TargetName="myMediaPlayer"
Source="{Binding MediaSource}"
RepeatBehavior="Forever" />
</Storyboard>
</UserControl.Resources>
<UserControl.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource myStoryboard}" />
</EventTrigger>
</UserControl.Triggers>
<Grid>
<MediaElement x:Name="mymediaPlayer" />
</Grid>
However, when I do this, it says that I need to "Must Specify URI." Dispatcher exception. In the viewmodel, I have a property like:
public Uri MediaSource
{
get { return _mediaSource; }
set
{
if (_oscilloscopeSource != value)
{
_mediaSource= value;
OnPropertyChanged("MediaSource");
}
}
}
It seems as though when the media player is loaded, it doesn't read the source from the binding. What gives?
In the constructor, I have:
_mediaSource = new Uri(#"C:\someMovie.mov", UriKind.Absolute);
Thanks.
Update
Can't get this to work so shooting in the dark now. Does moving the trigger to MediaElement make a difference?
<MediaElement x:Name="myMediaPlayer">
<MediaElement.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource myStoryboard}" />
</EventTrigger>
</MediaElement.Triggers>
</MediaElement>
I tried this out and it works for me. Possible reasons I can think of.
Do you have the DataContext set for the UserControl?
Setting _mediaSource directly won't call OnPropertyChanged since you're not setting the CLR property. Set MediaSource instead.
Your MediaElement is named mymediaPlayer and not myMediaPlayer as the TargetName. (Typo?)
Except for the MediaElement Name which I changed, my working xaml is identical to yours. This is my full code behind file
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public UserControl1()
{
InitializeComponent();
MediaSource = new Uri("C:\\C1.MOV");
this.DataContext = this;
}
private Uri _mediaSource;
public Uri MediaSource
{
get
{
return _mediaSource;
}
set
{
_mediaSource = value;
OnPropertyChanged("MediaSource");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I have a ListView that is set up with a MinHeight and a MaxHeight. The final height is determined by the number of items inside the list.
At the moment, when a list is added to the ItemsSource property of the ListView, the height jumps to the final height. Is there a way to animate this change in height, so that it's smooth?
Here's an example of something that does what you want (as I understand it). I'll call this "quick and dirty" and don't claim to have put a whole lot of thought into it.
public class CustomListView : ListView
{
public bool IsAttached
{
get { return (bool)GetValue(IsAttachedProperty); }
set { SetValue(IsAttachedProperty, value); }
}
// Using a DependencyProperty as the backing store for IsAttached.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsAttachedProperty =
DependencyProperty.Register("IsAttached",
typeof(bool),
typeof(CustomListView),
new UIPropertyMetadata(false));
}
public class ViewModel : INotifyPropertyChanged
{
public void PopulateItems()
{
Items = new List<string>();
for (var i = 0; i < 200; i++ )
{
Items.Add("The quick brown fox jumps over the lazy dog.");
}
InvokePropertyChanged(new PropertyChangedEventArgs("Items"));
IsAttached = true;
InvokePropertyChanged(new PropertyChangedEventArgs("IsAttached"));
}
public List<string> Items { get; private set; }
public bool IsAttached { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
private void InvokePropertyChanged(PropertyChangedEventArgs e)
{
var changed = PropertyChanged;
if (changed != null)
{
changed(this, e);
}
}
}
<Window x:Class="AnimateHeight.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:AnimateHeight"
Title="Window1" Height="300" Width="300">
<StackPanel>
<Button Width="100" Content="Add Items" Click="OnClickAddItems"/>
<local:CustomListView x:Name="VariableListView" ItemsSource="{Binding Items}" IsAttached="{Binding IsAttached}" >
<local:CustomListView.Style>
<Style TargetType="{x:Type local:CustomListView}">
<Setter Property="MinHeight" Value="50" />
<Setter Property="MaxHeight" Value="50" />
<Style.Triggers>
<Trigger Property="IsAttached" Value="true">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="(ListView.MaxHeight)"
To="150"
Duration="0:0:5"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</local:CustomListView.Style>
</local:CustomListView>
</StackPanel>
</Window>
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new ViewModel();
}
private void OnClickAddItems(object sender, RoutedEventArgs e)
{
((ViewModel)DataContext).PopulateItems();
}
}
UPDATE: You should be able to copy this into .cs and .xaml files and run it as an example application. To summarize what I'm doing: Set the MaxHeight property to something artificially low, in my case I just set it to the same value as the MinHeight. Then you can create a storyboard that animates the MaxHeight to its real value, which gives you the smooth transition effect. The trick is indicating when to start the animation, I use a dependency property in a subclassed ListView just because that seemed to be the easiest option to implement in a hurry. I just have to bind the dependency property to a value in my ViewModel and I can trigger the animation by changing that value (since I don't know of an easy way to trigger an animation based on a change to a ListView ItemsSource off the top of my head).