I'm trying to execute an animation on a cell in a datagrid when the value of the datagrid cell changes.
The datagrid itself is bound to an ObservableCollection of plain old CLR objects. In this case lets say the objects are 'Person' objects with properties for 'Firstname', 'Lastname' and 'Age'. The 'Person' class implements the INotifyPropertyChanged interface and each property has the appropriate call to onPropertyChanged in it's setter.
This is all fine. In the datagrid definition I've set my DataTemplate for drawing each cell and attached a datatrigger too ... as follows:
<DataGridTemplateColumn Header="FirstName">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border Name="templateBorder">
<TextBlock Name="templateTextBlock" Text="{Binding Path=FirstName}" />
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=FirstName}" Value="Richard">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard AutoReverse="True">
<DoubleAnimation Storyboard.TargetName="templateTextBlock" Storyboard.TargetProperty="Opacity" To=".1" Duration="0:0:.5" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
When an object in my ObservableCollection is updated (I changed the FirstName value) the datagrid is updated fine. As per the example above, if I changed the value of FirstName to 'Richard' then the animation is executed fine too.
My problem is that I need to run my animation regardless of what the new value of Firstname is. I've crawled the web but some far only seem to find examples of firing the trigger against a known value e.g. fire trigger when FirstName is 'Richard' as I've demonstrated in my example.
My question is how do I fire the datatrigger regardless of the value of the updated property? So basically how do I fire the datatrigger whenever the FirstName property is updated for a given row in the datagrid.
Many thanks.
Thanks to the pointers gained from the responses to this question I found the answer was to use an EventTrigger and the TargetUpdated RoutedEvent.
<DataTemplate>
<Border Name="templateBorder">
<TextBlock Name="templateTextBlock" Text="{Binding Path=FirstName, NotifyOnTargetUpdated=True}" />
</Border>
<DataTemplate.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard AutoReverse="True">
<DoubleAnimation Storyboard.TargetName="templateTextBlock" Storyboard.TargetProperty="Opacity" To=".1" Duration="0:0:.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Beyond the EventTrigger, the only other thing that was required was to set 'NotifyOnTargetUpdated=True' when setting up the binding for the textblock.
Thanks.
It looks like you need an EventTrigger "do X when an event occurs" instead of a DataTrigger.
Not tried this myself.. but it should be possible to raise your custom event FirstNameChanged and have the trigger-actions be executed in response to that.
<Storyboard x:Key="MessageStoryBoardEntry" FillBehavior="Stop">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="0:0:00.30" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:03" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:03.20" Value="1500"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="MessageStoryBoardExit" FillBehavior="Stop">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="0:0:0.001" Value="1500"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
You could try setting the DataContext of the TextBlock to the FirstName property, and then use the DataContextChanged event.
Or you could use the PropertyChanged event and filter for the property you want.
Either way I think you're going to have to use an event.
Could you hack something in with a value converter?
<DataTrigger Binding="{Binding Path=FirstName, Converter=FirstNameConverter}" Value="MakeItSo">
and
class FirstNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return "MakeItSo";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
...
}
}
I guess it depends on whether WPF calls the converter on every property change, or whether it evaluates the value first. I've not tried it, it's just a thought...
Related
I have a control that has its margin bound to a property of my view model:
<Grid Margin="{Binding Path=Property1, Converter={StaticResource Converter1}}"></Grid>
How do I get a smooth animation between successive updates to the Margin property? I want the margin to slide for a short amount of time instead of a discrete jump. Preferably a xaml solution.
Edit:
This is different than the other questions on this site, because I would need the "From" in a thickness animation to be bound to the previous value, and "To" to be bound to the updated value. It seems like a hack to just add another property to the view model for this.
Found the solution; the animation only needs to bind to the "From" and it will animate the way I want.
<Grid Margin="{Binding Path=Property1,
NotifyOnTargetUpdated=True,
Converter={StaticResource Converter1}}">
<Grid.Triggers><EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard><StoryBoard>
<ThicknessAnimation Storyboard.TargetProperty="Margin"
Duration="00:00:00.5"
From="{Binding Path="Property1" Converter={StaticResource Converter1}}"/>
</StoryBoard></BeginStoryboard>
</EventTrigger></Grid.Triggers>
</Grid>
You can use ThicknessAnimation:
<BeginStoryboard>
<Storyboard>
<!-- BorderThickness animates from left=1, right=1, top=1, and bottom=1 to
left=28, right=28, top=14, and bottom=14 over one second. -->
<ThicknessAnimation
Storyboard.TargetProperty="Margin"
Duration="0:0:1.5" FillBehavior="HoldEnd" From="1,1,1,1" To="28,14,28,14" />
</Storyboard>
</BeginStoryboard>
You just need to bind the properties From and To
When I add to combobox the property IsEditable="True" it automatically receive the "Auto Complete" behavior.
Is there a way to add to this combobox the "Auto Suggest" behavior?
I mean, when writing in the combobox opens a list of options with the above caption.
(If possible = without destroying the MVVM)
Here you go
I tried to add the auto suggest kind of behavior using standard combobox and animation
<ComboBox IsEditable="True">
<ComboBoxItem>Orange</ComboBoxItem>
<ComboBoxItem>Apple</ComboBoxItem>
<ComboBoxItem>Banana</ComboBoxItem>
<ComboBoxItem>Cherry</ComboBoxItem>
<ComboBox.Triggers>
<EventTrigger RoutedEvent="TextBoxBase.TextChanged">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsDropDownOpen">
<DiscreteBooleanKeyFrame Value="True" KeyTime="0:0:0"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ComboBox.Triggers>
</ComboBox>
Give this a try and let me know if this is what you are looking for, more sophisticated behavior might need some extra efforts
Add suggestion filter for combo Items
xaml
<ComboBox IsEditable="True"
ItemsSource="{Binding ComboItems}"
Text="{Binding ComboText,Mode=OneWayToSource}">
<ComboBox.Triggers>
<EventTrigger RoutedEvent="TextBoxBase.TextChanged">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsDropDownOpen">
<DiscreteBooleanKeyFrame Value="True"
KeyTime="0:0:0" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ComboBox.Triggers>
</ComboBox>
view model
public ICollectionView ComboItems{ get; set; }
public string ComboText
{
get
{
throw new NotImplementedException();
}
set
{
ComboItems.Filter = item => item.ToString().ToLower().Contains(value.ToLower());
}
}
you may need to filter based on your items types, above is for string values
to init the ComboItems
var myItems = new[] { "Apple", "Orange", "Cherry", "Banana" };
ComboItems = CollectionViewSource.GetDefaultView(myItems);
replace my items with your collection
How can I have a control's property to be set to a specific value, if a event of the same control fires?
Let's say I have an expander
<Expander Header="Click to expand" GotFocus="IsExpanded=True" />
And I want to set the IsExpanded Property to true, if it got Focus.
How can I do this in Xaml?
You can try to use binding, probably something like this:
<Expander IsExpanded="{Binding IsFocused, RelativeSource={RelativeSource Self}, Mode=OneWay}" />
Adrian's approach is the cleanest way to reach your goal. However, if you want to change a property when an event fires, you can try this:
<Expander Header="Click to expand">
<Expander.Style>
<Style TargetType="Expander">
<Style.Triggers>
<EventTrigger RoutedEvent="GotFocus">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Expander.IsExpanded)">
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</Expander.Style>
</Expander>
Note: this is purely from memory, and may not work as-is. But it should give you a good idea of how this could be accomplished.
The purpose of this tooltip is to show, the format of the string which must be entered.
The features I would like to achieve are:
The tooltip should be shown when the user places the cursor in the textbox, i.e. when the user tabs into the control.
The tooltip should update based on user input into the textbox (this can be achieved by binding).
The tooltip must persist until the user tabs out of the control.
I wanted to know if the standard tooltip as provided has configuration settings, properties, that can be used to achieve this,... in my research thus far I haven't found any. If the existing tooltip is not up to the task, which is very likely, I'd like some pointers, sample code to achieve this...
Thanks
Hasanain
Using a combination of event triggers, bindings, and minimal code-behind I managed to implement a behavior which would update the ToolTip while the user types into textbox; when the keyboard focus is lost the tooltip disappears.
Here is the xaml for the textbox:
<TextBox Grid.Column="0" x:Name="txtBxQckTkt" Margin="5,5,0,0" Width="250" ToolTipService.IsEnabled="True"
Text="{Binding QuickTicketText}">
<TextBox.Triggers>
<EventTrigger RoutedEvent="TextBox.GotKeyboardFocus">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames
Storyboard.TargetName="txtBxQckTktToolTip"
Storyboard.TargetProperty="IsOpen">
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="False"/>
<DiscreteBooleanKeyFrame KeyTime="0:0:0.0001" Value="True" />
</BooleanAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="txtBxQckTktToolTip"
Storyboard.TargetProperty="Placement">
<DiscreteObjectKeyFrame Value="{x:Static PlacementMode.Bottom}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="TextBox.LostKeyboardFocus">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames
Storyboard.TargetName="txtBxQckTktToolTip"
Storyboard.TargetProperty="IsOpen">
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True"/>
<DiscreteBooleanKeyFrame KeyTime="0:0:0.0001" Value="False" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBox.Triggers>
<TextBox.ToolTip>
<ToolTip x:Name="txtBxQckTktToolTip" Placement="Bottom" Content="{Binding ToolTip}">
</ToolTip>
</TextBox.ToolTip>
</TextBox>
Here is the code-behind:
txtBxQckTktToolTip.PlacementTarget = txtBxQckTkt;
_handler = (s, e) =>
{
var viewModel = DataContext as SingleTradeEntryViewModel;
if (viewModel == null) return;
viewModel.OnKeyup.Execute(txtBxQckTkt.Text);
};
txtBxQckTkt.KeyUp -= _handler;
txtBxQckTkt.KeyUp += _handler;
When the command (OnKeyup) executes, it raises a change notification for the ToolTip property bound as seen in the xaml.
Thanks
Hasanain
You might have to implement your own using the Popup Control. Here is some sample XAML to get you started:
<Button Width="120" Height="30" Name="btn">
<Popup IsOpen="True" StaysOpen="True" PlacementTarget="{Binding ElementName=btn}" Placement="Bottom">
<Button Width="120" Height="30" Content="Button In ToolTip"/>
</Popup>
</Button>
And here is some example code to get you started:
http://social.msdn.microsoft.com/forums/en-US/wpf/thread/845ffad0-4abf-4830-b206-03f7fe53f74b
2. ToolTip="{Binding Text, ElementName=textBox1, UpdateSourceTrigger=PropertyChanged}"
Here textBox1 is your textbox name and I have changed UpdateSourceTrigger to PropertyChanged so it updates your tooltip as you type.
3. ToolTipService.ShowDuration="12000"
Give this property a random time which is long enough to suit your needs.
I don't fully understand your first point but I think you need the tooltip to show in your gotfocus eventhandler. This can be achieved by something like in the gotfocus event.
ToolTip toolTip = ToolTipService.GetToolTip(textBox1) as ToolTip;
toolTip.IsOpen = true;
You could create a trigger that sets the ToolTip based on if the control has focus or not
I have a DataGrid with rows representing a host I'm doing pings to and a column called Lost which represents lost ICMP packets which over time increases in value. I have the whole INotifyPropertyChanged thing down and I'm seeing value increase. What I want to do is write a Style that'll change a row's background color from white to dark red progressively relative to the Lost column's value.
I would like, if it were possible, to write a Trigger or DataTrigger with a setter value set to a ValueConverter which would calculate the color needed, but so far I've been unsuccessful in writing a style that will update every time a Lost cell's value changes. I only see a difference in color when I load a new data context and switch back (just for testing).
Here's what I've tried:
<Style x:Key="DownStyle" TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="{Binding Converter={StaticResource BackgroundConverter}}" />
</Style>
I can't see this working with a DataTrigger since you have to specify a value anyways and the values are infinite (or Int32.MaxValue I guess), really. Although I also tried specifying a ValueConverter for the value property and that didn't work either.
Btw, I want to try to avoid code-behind if possible.
Edit:
Rick: I had tried doing something like:
<Style x:Key="DownStyle" TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource LostColumnValueConverter}}" Value="Somenumber">
<Setter Property="Background" Value="{Binding Converter={StaticResource BackgroundConverter}}" />
</DataTrigger>
</Style.Triggers>
</Style>
I think I understand the need to have the Trigger actually bind to something that's going to be changing (in this case I feel I'm forced to also use a ValueConverter to get the column's value, is there a more direct way?), but even if I do this, what do I specify as the DataTrigger's value?
Edit:
So in my case I went ahead and did the following (currently this would only modify the TextBlock's background):
<DataGridTemplateColumn Header="Lost" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Lost, NotifyOnTargetUpdated=True}">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding Converter={StaticResource BackgroundConverter}}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Which to me seems right, but only works once for some reason. I added a seperate TargetUpdated event handler right on the TextBlock definition to see if the event was indeed being called on every change and it is.
Something must be missing on my EventTrigger. Probably something to do with the Storyboard.
Anyways, this all seems incredibly verbose for something so simple, so I went ahead and went with the code-behind route.
I suppose Triggers and DataTriggers can't help in this case because datagrigger's value is unstable. And worse, it's not dependency property and you can't bind to it. It seems to me the better way is to use EventTrigger.
<Window.Resources>
<BeginStoryboard x:Key="bsbPing">
<Storyboard>
<DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="FontSize" To="{Binding Path=PingValue}" />
</Storyboard>
</BeginStoryboard>
</Window.Resources>
<Grid>
<StackPanel>
<TextBox Name="txbPingValue" Text="{Binding Path=PingValue}">
<TextBox.Triggers>
<EventTrigger RoutedEvent="TextBox.TextChanged">
<EventTrigger.Actions>
<StaticResource ResourceKey="bsbPing" />
</EventTrigger.Actions>
</EventTrigger>
</TextBox.Triggers>
</TextBox>
<Button Name="btnPing" Click="btnPing_Click">Ping</Button>
</StackPanel>
</Grid>
and code:
public partial class Window7 : Window
{
public Ping MyPing { get; set; }
public Window7()
{
InitializeComponent();
MyPing = new Ping { PingValue = 20.0 };
this.DataContext = MyPing;
}
private void btnPing_Click(object sender, RoutedEventArgs e)
{
MyPing.PingValue += 10;
}
}
public class Ping : INotifyPropertyChanged
{
private double pingValue;
public double PingValue
{
get
{
return pingValue;
}
set
{
pingValue = value;
NotifyPropertyChanged("PingValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
You are on the right track with the converter. The technique you are using is sometimes called "binning" which maps a large set of integers to a fixed small set of values. It is also used to create histograms.
As far as not updating, your binding expression has to be more specific to retrieve the Lost property, otherwise it won't be re-evaluated when Lost changes. Once you change it you'll also need to update your converter.