I have bound a ListBox to my ViewModel including the ListBox.SelectedItem. I want to change a visual state depending on if there is one selected or not, but The following doesn't update the state initially, so it stays in the wrong state:
<DataStateBehavior Binding="{Binding SelectedCamera}" Value="{x:Null}" TrueState="CameraSettingsUnselected" FalseState="CameraSettingsSelected"/>
Why is that and how to fix it?
The problem here seems to be that the binding initially evaluates to null and thus doesn't fire the change notification required for the evaluation and state change.
I've fixed it with the following subclass:
public class FixedDataStateBehavior: DataStateBehavior
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += (sender, routedEventArgs) =>
{
var bindingExpression = BindingOperations.GetBindingExpression(this, BindingProperty);
SetCurrentValue(BindingProperty,new object());
bindingExpression.UpdateTarget();
};
}
}
and used it like this:
<FixedDataStateBehavior Binding="{Binding SelectedCamera}" Value="{x:Null}" TrueState="CameraSettingsUnselected" FalseState="CameraSettingsSelected"/>
The answer above works, but I ended up creating a more generic Behavior class that would simply work with all bindings without needing to specify them individually.
public class RefreshDataContextBehavior : Behavior<FrameworkElement>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.Loaded += AssociatedObject_Loaded;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.Loaded -= AssociatedObject_Loaded;
}
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
var dc = this.AssociatedObject.DataContext;
this.AssociatedObject.DataContext = null;
this.AssociatedObject.DataContext = dc;
}
}
Then simply insert it into the XAML like so on the object which has the DataContext:
<i:Interaction.Behaviors>
<local:RefreshDataContextBehavior />
</i:Interaction.Behaviors>
Related
I have a style that I want to apply to a DataGrid. The DataGrid needs to run my custom sort code instead of the default DataGrid sort. The solution that I tried was as follows:
<Style TargetType="{x:Type DataGrid}" x:Key="FilteredDataGrid">
<EventSetter Event="Sorting" Handler="DataGrid_Sorting"/>
</Style>
And in the code-behind:
private void DataGrid_Sorting(object sender, DataGridSortingEventArgs e) {
e.Handled = true;
//Here is where I put the code for my custom sort.
}//DataGrid_Sorting
However, this code doesn't build. It seems to me that because the DataGrid.Sorting event is not a RoutedEvent, it can't be used in an EventSetter.
How can I customize the sorting for any DataGrid that has my style applied?
There is a workaround to provide a routed event when you only have a "normal" event:
Create an attached property that controls the event forwarding and an attached event that shall replace the original event. In order to do this, create a class DataGridEx (whatever class name you prefer) as a container for the attached property (DataGridEx.EnableSortingEvent) and event (DataGridEx.Sorting).
Also, create a custom RoutedEventArgs class that forwards the original sorting event args
public class DataGridExSortingEventArgs : RoutedEventArgs
{
public DataGridExSortingEventArgs(RoutedEvent routedEvent, DataGridSortingEventArgs sourceEventArgs) : base(routedEvent)
{
SourceEventArgs = sourceEventArgs;
}
public DataGridSortingEventArgs SourceEventArgs { get; set; }
}
public static class DataGridEx
{
public static bool GetEnableSortingEvent(DependencyObject obj)
{
return (bool)obj.GetValue(EnableSortingEventProperty);
}
public static void SetEnableSortingEvent(DependencyObject obj, bool value)
{
obj.SetValue(EnableSortingEventProperty, value);
}
// Setting this property to true enables the event forwarding from the DataGrid.Sorting event to the DataGridEx.Sorting RoutedEvent
public static readonly DependencyProperty EnableSortingEventProperty = DependencyProperty.RegisterAttached(
"EnableSortingEvent",
typeof(bool),
typeof(DataGridEx),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnEnableSortingChanged)));
private static void OnEnableSortingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is DataGrid dg)
{
if ((bool)e.NewValue)
dg.Sorting += Dg_Sorting;
else
dg.Sorting -= Dg_Sorting;
}
}
// When DataGrid.Sorting is called and DataGridEx.EnableSortingEvent is true, raise the DataGridEx.Sorting event
private static void Dg_Sorting(object sender, DataGridSortingEventArgs e)
{
if (sender is DataGrid dg && GetEnableSortingEvent(dg))
{
dg.RaiseEvent(new DataGridExSortingEventArgs(SortingEvent, e));
}
}
// When DataGridEx.EnableSortingEvent is true, the DataGrid.Sorting event will be forwarded to this routed event
public static readonly RoutedEvent SortingEvent = EventManager.RegisterRoutedEvent(
"Sorting",
// only effective on the DataGrid itself
RoutingStrategy.Direct,
typeof(RoutedEventHandler),
typeof(DataGridEx));
public static void AddSortingHandler(DependencyObject d, RoutedEventHandler handler)
{
if (d is DataGrid dg)
dg.AddHandler(SortingEvent, handler);
}
public static void RemoveSortingHandler(DependencyObject d, RoutedEventHandler handler)
{
if (d is DataGrid dg)
dg.RemoveHandler(SortingEvent, handler);
}
}
Now use those in your style (with local being the xmlns for the namespace where DataGridEx is defined):
<Style TargetType="DataGrid">
<Setter Property="local:DataGridEx.EnableSortingEvent" Value="True"/>
<EventSetter Event="local:DataGridEx.Sorting" Handler="DataGrid_Sorting"/>
</Style>
The handler
private void DataGrid_Sorting(object sender, RoutedEventArgs e)
{
if (e is DataGridExSortingEventArgs args)
{
// will prevent datagrid default sorting
args.SourceEventArgs.Handled = true;
}
// More stuff
}
I hope this is what you needed. Had to refresh my memory about attached stuff :)
I created a subclass of FrameworkElement that has a collection of Visuals :
public class GameElement : FrameworkElement
{
private VisualCollection Visuals { get; }
public GameElement()
{
this.KeyDown += this.OnKeyDown;
this.MouseDown += this.OnMouseDown;
}
private void OnKeyDown(object sender, KeyEventArgs keyEventArgs)
{
... // Does not get fired.
}
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
... // Does get fired.
}
protected override void OnRender(DrawingContext drawingContext)
{
// Draw a transparent background to catch mouse events (otherwise hit testing won't hit anything).
drawingContext.DrawRectangle(Brushes.Transparent, null, new Rect(0, 0, RenderSize.Width, RenderSize.Height));
}
protected override int VisualChildrenCount
{
get
{
return this.Visuals.Count;
}
}
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index >= this.Visuals.Count)
{
throw new ArgumentOutOfRangeException();
}
return this.Visuals[index];
}
}
I display this element in XAML with the following code :
<UserControl
x:Class="..."
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="Black">
<Grid Margin="10">
<local:GameElement x:Name="GameElement" ClipToBounds="True" />
</Grid>
</UserControl>
I have tried everything I can think of, but I just cannot get the KeyDown event to fire. The mostly used comment I find online has to do with focus. I have tried every combination of Focusable="True" and calling this.Focus(), but nothing works.
Anyone got any idea how to do this?
Thanks!
To be able to handle keys pressing your element should be focused.
Also try to derive it from control instead of FramworkElement if you can do that.
public class GameElement : Control
{
private VisualCollection Visuals { get; }
public GameElement()
{
this.KeyDown += this.OnKeyDown;
this.MouseDown += this.OnMouseDown;
}
private void OnKeyDown(object sender, KeyEventArgs keyEventArgs)
{
// Does get fired.
}
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
Focus();
}
protected override void OnRender(DrawingContext drawingContext)
{
// Draw a transparent background to catch mouse events (otherwise hit testing won't hit anything).
drawingContext.DrawRectangle(Brushes.Transparent, null, new Rect(0, 0, RenderSize.Width, RenderSize.Height));
}
protected override int VisualChildrenCount
{
get
{
return this.Visuals.Count;
}
}
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index >= this.Visuals.Count)
{
throw new ArgumentOutOfRangeException();
}
return this.Visuals[index];
}
}
I finally got it to work by registering a class handler that also handles handled events.
EventManager.RegisterClassHandler(typeof(Window), Keyboard.KeyDownEvent, new KeyEventHandler(OnKeyDown), true);
Is there an easy way in WPF to bind VisualStates to enum values? Kinda like DataStateBehavior, but for an Enum?
The best way is to just go ahead and implement a Behavior that does just that -
public class EnumStateBehavior : Behavior<FrameworkElement>
{
public object EnumProperty
{
get { return (object)GetValue(EnumPropertyProperty); }
set { SetValue(EnumPropertyProperty, value); }
}
// Using a DependencyProperty as the backing store for EnumProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EnumPropertyProperty =
DependencyProperty.Register("EnumProperty", typeof(object), typeof(EnumStateBehavior), new UIPropertyMetadata(null, EnumPropertyChanged));
static void EnumPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null) return;
EnumStateBehavior eb = sender as EnumStateBehavior;
VisualStateManager.GoToElementState(eb.AssociatedObject, e.NewValue.ToString(), true);
}
}
The usage is extremely simple - use as follows:
<i:Interaction.Behaviors>
<local:EnumStateBehavior EnumProperty="{Binding MyEnumProperty}" />
</i:Interaction.Behaviors>
You can do it in pure xaml by using a DataTrigger per possible enum value with each trigger calling GoToStateAction with a different state. See the example below. For more details take a look at
Enum driving a Visual State change via the ViewModel.
<i:Interaction.Triggers>
<ei:DataTrigger Binding="{Binding ConfirmedAnswerStatus}" Value="Unanswered">
<ei:GoToStateAction StateName="UnansweredState" UseTransitions="False" />
</ei:DataTrigger>
<ei:DataTrigger Binding="{Binding ConfirmedAnswerStatus}" Value="Correct">
<ei:GoToStateAction StateName="CorrectlyAnsweredState" UseTransitions="True" />
</ei:DataTrigger>
<ei:DataTrigger Binding="{Binding ConfirmedAnswerStatus}" Value="Incorrect">
<ei:GoToStateAction StateName="IncorrectlyAnsweredState" UseTransitions="True" />
</ei:DataTrigger>
</i:Interaction.Triggers>
There is a DataStateSwitchBehavior in SL that could be ported to WPF: Anyone have a DataStateSwitchBehavior for WPF4?
the syntax is pretty straightforward:
<is:DataStateSwitchBehavior Binding="{Binding Orientation}">
<is:DataStateSwitchCase Value="Left" State="LeftState"/>
<is:DataStateSwitchCase Value="Right" State="RightState"/>
<is:DataStateSwitchCase Value="Down" State="DownState"/>
<is:DataStateSwitchCase Value="Up" State="UpState"/>
<is:DataStateSwitchCase/>
I was having issues with the above EnumStateBehavior answer.
The PropertyChanged handler will first trigger when the AssociatedObject is null (since the binding has been set up but the Behavior hasn't been attached yet). Also, even when the behavior is first attached, the target elements of the VisualState animation may not yet exist since the behavior may have been attached before other child visual trees.
The solution was to use the Loaded event on the associated object to ensure the binding's initial state is set.
public class EnumStateBehavior : Behavior<FrameworkElement>
{
public static readonly DependencyProperty BindingProperty =
DependencyProperty.Register(nameof(Binding), typeof(object), typeof(EnumStateBehavior), new UIPropertyMetadata(null, BindingPropertyChanged));
public object Binding
{
get { return (object)GetValue(BindingProperty); }
set { SetValue(BindingProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.Loaded += AssociatedObject_Loaded;
}
protected override void OnDetaching()
{
this.AssociatedObject.Loaded -= AssociatedObject_Loaded;
base.OnDetaching();
}
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
if (Binding != null)
GoToState();
}
private void GoToState()
{
VisualStateManager.GoToElementState(this.AssociatedObject, Binding.ToString(), true);
}
private static void BindingPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var eb = (EnumStateBehavior)sender;
if (e.NewValue == null || eb.AssociatedObject == null || !eb.AssociatedObject.IsLoaded)
return;
eb.GoToState();
}
}
I want to show the selected item in a list view automatically(it isn't possible to show all items without scrolling).
this.listView.SelectedIndex = 999; selects of course an item, but it doesn't show it.
what can I use to show it automatically ?
kind regards, jeff
You can do this:-
listview.ScrollIntoView(listview.SelectedItem);
Scroll WPF ListBox to the SelectedItem set in code in a view model
Install a nuget package System.Windows.Interactivity.WPF, create a class like following:
public class ScrollToSelectedListBoxItemBehaviour: Behavior<ListBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += AssociatedObjectOnSelectionChanged;
AssociatedObject.IsVisibleChanged += AssociatedObjectOnIsVisibleChanged;
}
protected override void OnDetaching()
{
AssociatedObject.SelectionChanged -= AssociatedObjectOnSelectionChanged;
AssociatedObject.IsVisibleChanged -= AssociatedObjectOnIsVisibleChanged;
base.OnDetaching();
}
private static void AssociatedObjectOnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ScrollIntoFirstSelectedItem(sender);
}
private static void AssociatedObjectOnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
ScrollIntoFirstSelectedItem(sender);
}
private static void ScrollIntoFirstSelectedItem(object sender)
{
if (!(sender is ListBox listBox))
return;
var selectedItems = listBox.SelectedItems;
if (selectedItems.Count > 0)
listBox.ScrollIntoView(selectedItems[0]);
}
}
Add this behavior class to the xaml:
<ListView ItemsSource="{Binding Items}">
<i:Interaction.Behaviors>
<behaviors:ScrollToSelectedListBoxItemBehaviour />
</i:Interaction.Behaviors>
</ListView>
Check out this one:
Scroll WPF Listview to specific line
This might help you, i'm not sure if it's what you're looking for but it brings the selected item into view and scrolls to it for you if necessary.
int selectedIndex = listView.Items.IndexOf((listView.SelectedItems[0]))
listView.Items[selectedIndex].EnsureVisible();
I have a Silverlight 2 application that validates data OnTabSelectionChanged. Immediately I began wishing that UpdateSourceTrigger allowed more than just LostFocus because if you click the tab without tabbing off of a control the LINQ object is not updated before validation.
I worked around the issue for TextBoxes by setting focus to another control and then back OnTextChanged:
Private Sub OnTextChanged(ByVal sender As Object, ByVal e As TextChangedEventArgs)
txtSetFocus.Focus()
sender.Focus()
End Sub
Now I am trying to accomplish the same sort of hack within a DataGrid. My DataGrid uses DataTemplates generated at runtime for the CellTemplate and CellEditingTemplate. I tried writing the TextChanged="OnTextChanged" into the TextBox in the DataTemplate, but it is not triggered.
Anyone have any ideas?
You can do it with a behavior applied to the textbox too
// xmlns:int is System.Windows.Interactivity from System.Windows.Interactivity.DLL)
// xmlns:behavior is your namespace for the class below
<TextBox Text="{Binding Description,Mode=TwoWay,UpdateSourceTrigger=Explicit}">
<int:Interaction.Behaviors>
<behavior:TextBoxUpdatesTextBindingOnPropertyChanged />
</int:Interaction.Behaviors>
</TextBox>
public class TextBoxUpdatesTextBindingOnPropertyChanged : Behavior<TextBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.TextChanged += new TextChangedEventHandler(TextBox_TextChanged);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.TextChanged -= TextBox_TextChanged;
}
void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var bindingExpression = AssociatedObject.GetBindingExpression(TextBox.TextProperty);
bindingExpression.UpdateSource();
}
}
This blog post shows how to update the source of a textbox explicitly using attached property:
http://www.thomasclaudiushuber.com/blog/2009/07/17/here-it-is-the-updatesourcetrigger-for-propertychanged-in-silverlight/
You could easily modify it to work with other controls as well...
I ran into this same problem using MVVM and Silverlight 4. The problem is that the binding does not update the source until after the textbox looses focus, but setting focus on another control doesn't do the trick.
I found a solution using a combination of two different blog posts. I used the code from Patrick Cauldwell's DefaultButtonHub concept, with one "SmallWorkaround" from SmallWorkarounds.net
http://www.cauldwell.net/patrick/blog/DefaultButtonSemanticsInSilverlightRevisited.aspx
www.smallworkarounds.net/2010/02/elementbindingbinding-modes.html
My change resulted in the following code for the DefaultButtonHub class:
public class DefaultButtonHub
{
ButtonAutomationPeer peer = null;
private void Attach(DependencyObject source)
{
if (source is Button)
{
peer = new ButtonAutomationPeer(source as Button);
}
else if (source is TextBox)
{
TextBox tb = source as TextBox;
tb.KeyUp += OnKeyUp;
}
else if (source is PasswordBox)
{
PasswordBox pb = source as PasswordBox;
pb.KeyUp += OnKeyUp;
}
}
private void OnKeyUp(object sender, KeyEventArgs arg)
{
if (arg.Key == Key.Enter)
if (peer != null)
{
if (sender is TextBox)
{
TextBox t = (TextBox)sender;
BindingExpression expression = t.GetBindingExpression(TextBox.TextProperty);
expression.UpdateSource();
}
((IInvokeProvider)peer).Invoke();
}
}
public static DefaultButtonHub GetDefaultHub(DependencyObject obj)
{
return (DefaultButtonHub)obj.GetValue(DefaultHubProperty);
}
public static void SetDefaultHub(DependencyObject obj, DefaultButtonHub value)
{
obj.SetValue(DefaultHubProperty, value);
}
// Using a DependencyProperty as the backing store for DefaultHub. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DefaultHubProperty =
DependencyProperty.RegisterAttached("DefaultHub", typeof(DefaultButtonHub), typeof(DefaultButtonHub), new PropertyMetadata(OnHubAttach));
private static void OnHubAttach(DependencyObject source, DependencyPropertyChangedEventArgs prop)
{
DefaultButtonHub hub = prop.NewValue as DefaultButtonHub;
hub.Attach(source);
}
}
This should be included in some sort of documentation for Silverlight :)
I know it's old news... but I got around this by doing this:
Text="{Binding Path=newQuantity, UpdateSourceTrigger=PropertyChanged}"