I'm trying to create a style which will make all my DataGrids select row -1 when they lose focus. I'm doing:
<Style TargetType="{x:Type DataGrid}">
<Style.Triggers>
<EventTrigger RoutedEvent="DataGrid.LostFocus">
<BeginStoryboard>
<Storyboard>
<Int32AnimationUsingKeyFrames Storyboard.TargetProperty="(DataGrid.SelectedIndex)">
<DiscreteInt32KeyFrame KeyTime="00:00:00" Value="-1" />
</Int32AnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
It works only for the first time the focus is lost, but on the second time program crashes because of type conversion ecxeption. Is it possible without code behind?
According to my researches, attached behavior is the only acceptable solution for me. Hope this will help somebody more:
public class DataGridBehavior
{
public static readonly DependencyProperty IsDeselectOnLostFocusProperty =
DependencyProperty.RegisterAttached("IsDeselectOnLostFocus", typeof(bool), typeof(DataGridBehavior), new UIPropertyMetadata(false, PropertyChangedCallback));
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var dg = dependencyObject as DataGrid;
if (dg == null)
return;
if (e.NewValue is bool == false)
return;
if ((bool)e.NewValue)
dg.LostFocus += dg_LostFocus;
}
static void dg_LostFocus(object sender, RoutedEventArgs e)
{
(sender as DataGrid).SelectedIndex = -1;
}
public static bool GetIsDeselectOnLostFocus(DataGrid dg)
{
return(bool)dg.GetValue(IsDeselectOnLostFocusProperty);
}
public static void SetIsDeselectOnLostFocus(DataGrid dg, bool value)
{
dg.SetValue(IsDeselectOnLostFocusProperty, value);
}
}
Then:
<Style TargetType="{x:Type DataGrid}">
<Setter Property="helpers:DataGridBehavior.IsDeselectOnLostFocus" Value="True"/>
</Style>
A better way to achieve your actual goal of de-selecting the selected item is to bind an object of the same type as those in the collection data bound to the DataGrid.ItemsSource property to the DataGrid.SelectedItem property. When you want to deselect the selected item, you simply set this object to null:
<DataGrid ItemsSource="{Binding Items}" SelectedItem="{Binding Item}" />
In view model:
Item = null; // de-selects the selected item
Related
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));
}
}
We're looking for a way to base a WPF trigger in XAML on whether or not we're in a drag-drop operation. Depending on if we are or not, we want different hovering behaviors which is why this is needed.
The only way I can think of is to handle the drag start and end events and manually track the state, but that requires a code-behind, not pure XAML. Plus it seems like complete overkill, especially since we'd have to do it on every potential drop target which is a real pain.
So is there an easy way to say 'Hey... I'm in a drag-drop operation so make this trigger active' or am I out of luck here?
Update
To clarify what we are trying to do, currently in pure XAML, you can create a style, then set a style trigger to examine the IsMouseOver property to say, draw a background highlight. Well, we want to do this, but we want to say if 'IsMouseOver' is true and if IsDragging = true then apply this trigger.
I've just had this problem, my solution consists of using an attached property that supplies the missing IsDragging:
Define the attached Property
public static readonly DependencyProperty IsDraggingProperty =
DependencyProperty.RegisterAttached
(
"IsDragging",
typeof(bool),
typeof(ClassContainingThisProperty),
new UIPropertyMetadata(false)
);
public static bool GetIsDragging(DependencyObject source)
{
return (bool)source.GetValue(IsDraggingProperty);
}
public static void SetIsDragging(DependencyObject target, bool value)
{
target.SetValue(IsDraggingProperty, value);
}
Create this extension methods to help you set the Property
public static TParent FindParent<TParent>(this DependencyObject child) where TParent : DependencyObject
{
DependencyObject current = child;
while(current != null && !(current is TParent))
{
current = VisualTreeHelper.GetParent(current);
}
return current as TParent;
}
public static void SetParentValue<TParent>(this DependencyObject child, DependencyProperty property, object value) where TParent : DependencyObject
{
TParent parent = child.FindParent<TParent>();
if(parent != null)
{
parent.SetValue(property, value);
}
}
Handle DragDrop events according to the Control used (e.g. ListView), to set the attached property on the elements.
private void OnDragEnter(object sender, DragEventArgs e)
{
DependencyObject source = e.OriginalSource as DependencyObject;
if (source != null)
{
source.SetParentValue<ListViewItem>(ClassContainingTheProperty.IsDraggingProperty, true);
}
}
private void OnDragLeave(object sender, DragEventArgs e)
{
DependencyObject source = e.OriginalSource as DependencyObject;
if(source != null)
{
source.SetParentValue<ListViewItem>(ClassContainingTheProperty.IsDraggingProperty, false);
}
}
private void OnDrop(object sender, DragEventArgs e)
{
DependencyObject source = e.OriginalSource as DependencyObject;
if(source != null)
{
source.SetParentValue<ListViewItem>(ClassContainingTheProperty.IsDraggingProperty, false);
}
}
Use the Property in your Trigger
<ControlTemplate TargetType="{x:Type ListViewItem}">
<ControlTemplate.Triggers>
<Trigger Property="namespace:ClassContainingTheProperty.IsDragging" Value="True">
<Setter Property="Background" Value="White" />
<Setter Property="BorderBrush" Value="Black" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
I've only ever seen drag-drop implemented with event handlers, so I think you're out of luck. I suggest you create your own dependency property to indicate a drag drop in progress.
if it is a real DragDrop event just watch for the DragOver event...
<EventTrigger RoutedEvent="DragOver">
<BeginStoryboard>
<Storyboard Storyboard.TargetProperty="Fill.Color">
<ColorAnimation From="White" To="Black" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
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).
I've adopted what appears to be the standard way of validating textboxes in WPF using the IDataErrorInfo interface and styles as shown below. However, how can I disable the Save button when the page becomes invalid? Is this done somehow through triggers?
Default Public ReadOnly Property Item(ByVal propertyName As String) As String Implements IDataErrorInfo.Item
Get
Dim valid As Boolean = True
If propertyName = "IncidentCategory" Then
valid = True
If Len(IncidentCategory) = 0 Then
valid = False
End If
If Not valid Then
Return "Incident category is required"
End If
End If
Return Nothing
End Get
End Property
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="3" />
<Setter Property="Height" Value="23" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder Name="MyAdorner" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
A couple of things:
First, I would recommend using the RoutedCommand ApplicationCommands.Save for implementing the handling of the save button.
If you haven't checked out the WPF Command model, you can get the scoop here.
<Button Content="Save" Command="Save">
Now, to implement the functionality, you can add a command binding to the Window/UserControl or to the Button itself:
<Button.CommandBindings>
<CommandBinding Command="Save"
Executed="Save_Executed" CanExecute="Save_CanExecute"/>
</Button.CommandBindings>
</Button>
Implement these in code behind:
private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
}
private void Save_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
}
In Save_CanExecute, set e.CanExecute based on the validity of the binding on the text box.
If you want to implement using the MVVM (Model-View-ViewModel) design pattern, check out Josh Smith's post on CommandSinkBinding.
One final note: If you want the enable/disable to be updated as soon as the value in the TextBox is changed, set UpdateSourceTrigger="PropertyChanged" on the binding for the TextBox.
EDIT: If you want to validate/invalidate based on all of the bindings in the control, here are a few suggestions.
1) You are already implementing IDataErrorInfo. Try implementing the IDataErrorInfo.Error property such that it returns the string that is invalid for all of the properties that you are binding to. This will only work if your whole control is binding to a single data object. Set e.CanExecute = string.IsNullOrEmpty(data.Error);
2) Use reflection to get all of the public static DependencyProperties on the relevant controls. Then call BindingOperations.GetBindingExpression(relevantControl, DependencyProperty) in a loop on each property so you can test the validation.
3) In the constructor, manually create a collection of all bound properties on nested controls. In CanExecute, iterate through this collection and validate each DependencyObject/DepencyProperty combination by using BindingOperation.GetBindingExpression() to get expressions and then examining BindingExpression.HasError.
I've created attached property just for this:
public static class DataErrorInfoHelper
{
public static object GetDataErrorInfo(ButtonBase obj)
{
return (object)obj.GetValue(DataErrorInfoProperty);
}
public static void SetDataErrorInfo(ButtonBase obj, object value)
{
obj.SetValue(DataErrorInfoProperty, value);
}
// Using a DependencyProperty as the backing store for DataErrorInfo. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataErrorInfoProperty =
DependencyProperty.RegisterAttached("DataErrorInfo", typeof(object), typeof(DataErrorInfoHelper), new PropertyMetadata(null, OnDataErrorInfoChanged));
private static void OnDataErrorInfoChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var button = d as ButtonBase;
if (button.Tag == null)
button.Tag = new DataErrorInfoContext { Button = button };
var context = button.Tag as DataErrorInfoContext;
if(e.OldValue != null)
{
PropertyChangedEventManager.RemoveHandler(((INotifyPropertyChanged)e.OldValue), context.Handler, string.Empty);
}
var inotify = e.NewValue as INotifyPropertyChanged;
if (inotify != null)
{
PropertyChangedEventManager.AddHandler(inotify, context.Handler, string.Empty);
context.Handler(inotify, new PropertyChangedEventArgs(string.Empty));
}
}
private class DataErrorInfoContext
{
public ButtonBase Button { get; set; }
public void Handler(object sender, PropertyChangedEventArgs e)
{
var dei = sender as IDataErrorInfo;
foreach (var property in dei.GetType().GetProperties())
{
if (!string.IsNullOrEmpty(dei[property.Name]))
{
Button.IsEnabled = false;
return;
}
}
Button.IsEnabled = string.IsNullOrEmpty(dei.Error);
}
}
}
I'm using it like this on my forms:
<TextBlock Margin="2">e-mail:</TextBlock>
<TextBox Margin="2" Text="{Binding Email, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
<!-- other databindings--->
<Button Margin="2" local:DataErrorInfoHelper.DataErrorInfo="{Binding}" Commands="{Binding SaveCommand}">Create account</Button>
I can trigger property settings on my ListBoxItem template based on properties of underlying data object using DataTrigger with something like this
<DataTrigger Binding="{Binding Path=IsMouseOver}" Value="true">
<Setter TargetName="ItemText" Property="TextBlock.TextDecorations" Value="Underline">
</Setter>
</DataTrigger>
But what if I want to do the opposite? I mean set a property value on the underlying data object based on property value of my ListBoxItem. Something like:
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="MyClass.IsHilited" Value="True"></Setter>
</Trigger>
Is there a mechanism for something like this or what would be the recommended approach to deal with situations like this?
Thanks.
I think that you could use an EventSetter to do in XAML what Josh G suggested in code. Maybe create one for the MouseEnter and MouseLeave events, and style the control appropriately for each?
Update: You can set up the events like this:
<ListBox>
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="MouseEnter" Handler="OnListBoxItemMouseEnter" />
<EventSetter Event="MouseLeave" Handler="OnListBoxItemMouseLeave" />
</Style>
</ListBox.Resources>
<ListBoxItem>Item 1</ListBoxItem>
<ListBoxItem>Item 2</ListBoxItem>
<ListBoxItem>Item 3</ListBoxItem>
<ListBoxItem>Item 4</ListBoxItem>
<ListBoxItem>Item 5</ListBoxItem>
</ListBox>
The Style registers for the MouseEnter and MouseLeave events for all ListBoxItems defined in that ListBox.
And then in your code behind file:
private void OnListBoxItemMouseEnter(object sender, RoutedEventArgs e)
{
ListBoxItem lvi = sender as ListBoxItem;
if(null != lvi)
{
lvi.Foreground = new SolidColorBrush(Colors.Red);
}
}
private void OnListBoxItemMouseLeave(object sender, RoutedEventArgs e)
{
ListBoxItem lvi = sender as ListBoxItem;
if(null != lvi)
{
lvi.Foreground = new SolidColorBrush(Colors.Black);
}
}
These handlers set the color of the text of the ListBoxItem to red when the mouse is over the item, and back to black when the mouse leaves it.
WPF triggers are intended for causing visual changes. Setter objects within triggers cause property changes on the control.
If you want to respond to an event (like an EventTrigger), you could always simply subscribe to the event in code and then set the data property in the handler.
You could use MouseEnter and MouseLeave in this way. For example:
listBox.MouseEnter += listBox_MouseEnter;
listBox.MouseLeave += listBox_MouseLeave;
void listBox_MouseEnter(object sender, MouseEventArgs e)
{
listBox.MyClass.IsHilited = true;
}
void listBox_MouseLeave(object sender, MouseEventArgs e)
{
listBox.MyClass.IsHilited = false;
}
Some properties on a control you could bind the property of the data object to, like so:
Binding myBind = new Binding("IsHilited");
myBind.Source = listBox.DataContext;
listBox.SetBinding(listBox.IsEnabled, myBind);
You can't use IsMouseOver in a binding, however.
If you create a custom control you can have even greater flexibility to build a binding like this into the control. You could create a custom depency property and sync it with the data property in the DependencyPropertyChanged handler. You could then set this dependency property with a WPF trigger.
Here's an example:
public static readonly DependencyProperty IsHilitedProperty =
DependencyProperty.Register("IsHilited", typeof(bool), typeof(CustomListBox),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsHilitedChanged)));
public double IsHilited
{
get
{
return (bool)GetValue(IsHilitedProperty);
}
set
{
SetValue(IsHilitedProperty, value);
}
}
private static void OnIsHilitedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
CustomListBox box = obj as CustomListBox;
if (box != null)
box.MyClass.IsHilited = box.IsHilited;
// Or:
// Class myClass = box.DataContext as Class;
// myClass.IsHilited = box.isHilited;
}
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="IsHilited" Value="True"/>
</Trigger>