I am trying to change the code below from being an event setter to an attached property (just so I can clean up the code behind). I get an error in the setter saying the value cannot be null, but I don't see why yet.
Forgetting for a second whether this is a good idea or not, can someone help me get the attached property right?
Cheers,
Berryl
EventSetter (works but with code behind as shown)
<!-- SINGLE CLICK EDITING -->
<Style TargetType="{x:Type DataGridCell}">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnPreviewMouseLeftButtonDown"/>
</Style>
private void OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var cell = sender as DataGridCell;
cell.Activate();
}
Property setter (error)
<!-- SINGLE CLICK EDITING -->
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="begavior:DataGridCellProperties.SingleClickToEdit" Value="True"/>
</Style>
public class DataGridCellProperties
{
public static readonly DependencyProperty SingleClickToEditProperty =
DependencyProperty.RegisterAttached("SingleClickToEditProperty",
typeof(bool), typeof(DataGridCellProperties),
new PropertyMetadata(false, OnSingleClickToEditPropertyChanged));
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(DataGridCell))]
public static bool GetSingleClickToEdit(DataGridCell obj) { return (bool)obj.GetValue(SingleClickToEditProperty); }
public static void SetSingleClickToEdit(DataGridCell obj, bool value) { obj.SetValue(SingleClickToEditProperty, value); }
private static void OnSingleClickToEditPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var sender = obj as UIElement;
if (sender == null) return;
if ((bool)e.NewValue)
{
sender.PreviewMouseLeftButtonDown += OnPreviewMouseLeftButtonDown_EditCell;
}
else
{
sender.PreviewMouseLeftButtonDown -= OnPreviewMouseLeftButtonDown_EditCell;
}
}
private static void OnPreviewMouseLeftButtonDown_EditCell(object sender, MouseButtonEventArgs e)
{
var cell = sender as DataGridCell;
cell.Activate();
}
}
"SingleClickToEditProperty" in your d-prop registration should be "SingleClickToEdit".
Related
Is there a way to attach a handler for the IsVisibleChanged event for a DataGridRow in a DataGridRow style definition? That is, is there a way to do something like the following:
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<EventSetter Event="IsVisibleChanged" Handler="OnIsVisibleChanged"/>
</Style>
</DataGrid.RowStyle>
The above won't work because EventSetter can only be applied to RoutedEvents and not regular CLR events, like IsVisibleChanged.
We'll have to make an attached property and an event.
using System;
using System.Windows;
namespace CommonCore.AttachedEvents
{
public static class UIElementHelper
{
public static readonly RoutedEvent IsVisibleChangedEvent = EventManager.RegisterRoutedEvent(
"IsVisibleChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<bool>), typeof(UIElementHelper));
public static void AddIsVisibleChangedHandler(DependencyObject dependencyObject, RoutedPropertyChangedEventHandler<bool> handler)
{
if (dependencyObject is not UIElement uiElement)
return;
uiElement.AddHandler(IsVisibleChangedEvent, handler);
}
private static void RaiseIsVisibleChangedEvent(object sender, DependencyPropertyChangedEventArgs e)
{
((UIElement)sender).RaiseEvent(new RoutedPropertyChangedEventArgs<bool>((bool)e.OldValue, (bool)e.NewValue, IsVisibleChangedEvent));
}
public static void RemoveIsVisibleChangedHandler(DependencyObject dependencyObject, RoutedPropertyChangedEventHandler<bool> handler)
{
if (dependencyObject is not UIElement uiElement)
return;
uiElement.RemoveHandler(IsVisibleChangedEvent, handler);
}
public static bool GetRaiseIsVisibleChanged(UIElement uiElement)
{
return (bool)uiElement.GetValue(RaiseIsVisibleChangedProperty);
}
public static void SetRaiseIsVisibleChanged(UIElement uiElement, bool value)
{
uiElement.SetValue(RaiseIsVisibleChangedProperty, value);
}
// Using a DependencyProperty as the backing store for RaiseIsVisibleChanged. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RaiseIsVisibleChangedProperty =
DependencyProperty.RegisterAttached(
"RaiseIsVisibleChanged",
typeof(bool),
typeof(UIElementHelper),
new PropertyMetadata(false, OnRaiseIsVisibleChanged));
private static void OnRaiseIsVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not UIElement uiElement)
{
throw new InvalidOperationException("Implemented only for UIElement.");
}
if ((bool)e.NewValue)
{
uiElement.IsVisibleChanged += RaiseIsVisibleChangedEvent;
}
else
{
uiElement.IsVisibleChanged -= RaiseIsVisibleChangedEvent;
}
}
}
}
Their use:
<StackPanel>
<CheckBox x:Name="checkBox"
Content="Visibility"
IsChecked="False"/>
<Border Background="AliceBlue" Padding="10" Margin="10">
<Grid Height="20" Background="Aqua"
Visibility="{Binding IsChecked,
ElementName=checkBox, Converter={commcnvs:BooleanToVisibility}}">
<FrameworkElement.Style>
<Style TargetType="Grid">
<Setter Property="aev:UIElementHelper.RaiseIsVisibleChanged"
Value="True"/>
<EventSetter Event="aev:UIElementHelper.IsVisibleChanged"
Handler="OnIsVisibleChanged"/>
</Style>
</FrameworkElement.Style>
</Grid>
</Border>
</StackPanel>
private void OnIsVisibleChanged(object sender, RoutedPropertyChangedEventArgs<bool> args)
{
MessageBox.Show($"OldValu = {args.OldValue}; NewValue = {args.NewValue};");
}
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 would like to trigger the method SelectAllText() when the textbox background color is red. How can I bind to code behind.
xaml:
<TextBox Grid.Column="1" Grid.Row="0" Text="Text" MouseEnter="Test1MouseEnter" Background="{Binding TxtBoxcolor, Mode=OneWay}" Name="txbName">
<TextBox.Style>
<Style>
<Style.Triggers>
<Trigger Property="TextBox.Background" Value="Red">
<!--Trigger code behind-->
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
code behind:
public void SelectAllText()
{
txbName.SelectAll();
}
It's possible in your case to handle Changed event on the background in the code behind?
txbName.Background.Changed += Background_Changed;
and in the Background_Changed
private void Background_Changed(object sender, EventArgs e)
{
var brush = sender as Brush;
if(brush!=null)
{
if(brush == Brushes.Red)
{
txbName.SelectAll();
}
}
}
Here's a way to do this with an attached event. It can only handle raising change events for one property per control. To raise events on value changes for multiple properties, you'd need an attached property that's a collection of some object with a property name and an event, which would be more complicated to write. This really just demonstrates the concept, but it's sufficient for the specific problem you have in front of you right now.
public static class PropertyChange
{
public static readonly RoutedEvent PropertyChangeEvent =
EventManager.RegisterRoutedEvent("PropertyChangeEvent", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(PropertyChange));
public static void AddPropertyChangeEventHandler(DependencyObject d, RoutedEventHandler handler)
{
var uie = d as UIElement;
if (uie != null)
{
uie.AddHandler(PropertyChange.PropertyChangeEvent, handler);
}
}
public static void RemovePropertyChangeEventHandler(DependencyObject d, RoutedEventHandler handler)
{
var uie = d as UIElement;
if (uie != null)
{
uie.RemoveHandler(PropertyChange.PropertyChangeEvent, handler);
}
}
#region PropertyChange.PropertyName Attached Property
public static String GetPropertyName(UIElement obj)
{
return (String)obj.GetValue(PropertyNameProperty);
}
public static void SetPropertyName(UIElement obj, String value)
{
obj.SetValue(PropertyNameProperty, value);
}
public static readonly DependencyProperty PropertyNameProperty =
DependencyProperty.RegisterAttached("PropertyName", typeof(String), typeof(PropertyChange),
new PropertyMetadata(null, PropertyName_PropertyChanged));
private static void PropertyName_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = d as UIElement;
var oldProperty = e.OldValue as String;
var newProperty = e.NewValue as String;
if (oldProperty != null)
{
var dpd = DependencyPropertyDescriptor.FromName(oldProperty, d.GetType(), d.GetType());
dpd.RemoveValueChanged(d, PropertyChangedHandler);
}
if (newProperty != null)
{
var dpd = DependencyPropertyDescriptor.FromName(newProperty, d.GetType(), d.GetType());
dpd.AddValueChanged(d, PropertyChangedHandler);
}
}
private static void PropertyChangedHandler(object component, EventArgs args)
{
var uie = component as UIElement;
uie.RaiseEvent(new RoutedEventArgs(PropertyChange.PropertyChangeEvent, uie));
}
#endregion PropertyChange.PropertyName Attached Property
}
Demo
XAML
<TextBox
Width="100"
x:Name="TestTextBox"
Text="Blah blah blah"
local:PropertyChange.PropertyName="Background"
local:PropertyChange.PropertyChangeEvent="TestTextBox_PropertyChangeEvent"
>
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Code behind
private void TestTextBox_PropertyChangeEvent(object sender, RoutedEventArgs e)
{
var tb = (TextBox)sender;
var x = tb.Background as SolidColorBrush;
// Instead of examining the background color, I would much prefer to look directly
// at the validation: What happens if you decide to change the error background color
// to a darker shade of red? Or a GradientBrush? A cosmetic decision like that should
// not affect program behavior.
//
// But you didn't give any clues about how your validation is implemented, so that's
// your problem not mine.
if (x != null && x.Color == Colors.Red)
{
tb.Focus();
tb.SelectAll();
}
}
I am aware of the IsOverflowOpen and HasOverflowItems properties but I am looking for a way to tell whether the item (button, radiobutton...) has moved into the ToolBarOverflowPanel so I can use a trigger to change its style.
I need this to be able to reproduce some UWP ToolBar styles (Windows 10 Mail, Word, Excel...). I have successfully reproduced most of the style and the only missing bit is to be able to changed the style of my item when it is in the overflow panel.
On the screenshot of what I am trying to reproduce, you can clearly see that the Special Ident and Line Spacing buttons have changed style based on whether they are displayed or overflowed.
You can't do it with xaml only. You have to either use the code behind or create some attached properties.
Here is the solution with the AttachedProperty:
First you need to create an helper class exposing 2 properties:
The IsInOverflowPanel read-only property that you will use to trigger the style change.
The TrackParentPanel property, which is the enable/disable mechanism.
Here is the implementation:
public static class ToolBarHelper
{
public static readonly DependencyPropertyKey IsInOverflowPanelKey =
DependencyProperty.RegisterAttachedReadOnly("IsInOverflowPanel", typeof(bool), typeof(ToolBarHelper), new PropertyMetadata(false));
public static readonly DependencyProperty IsInOverflowPanelProperty = IsInOverflowPanelKey.DependencyProperty;
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static bool GetIsInOverflowPanel(UIElement target)
{
return (bool)target.GetValue(IsInOverflowPanelProperty);
}
public static readonly DependencyProperty TrackParentPanelProperty =
DependencyProperty.RegisterAttached("TrackParentPanel", typeof(bool), typeof(ToolBarHelper),
new PropertyMetadata(false, OnTrackParentPanelPropertyChanged));
public static void SetTrackParentPanel(DependencyObject d, bool value)
{
d.SetValue(TrackParentPanelProperty, value);
}
public static bool GetTrackParentPanel(DependencyObject d)
{
return (bool)d.GetValue(TrackParentPanelProperty);
}
private static void OnTrackParentPanelPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as UIElement;
if (element != null)
{
bool newValue = (bool)e.NewValue;
if (newValue)
{
element.LayoutUpdated += (s, arg) => OnControlLayoutUpdated(element);
}
}
}
private static void OnControlLayoutUpdated(UIElement element)
{
var isInOverflow = TreeHelper.FindParent<ToolBarOverflowPanel>(element) != null;
element.SetValue(IsInOverflowPanelKey, isInOverflow);
}
}
public static class TreeHelper
{
public static T FindParent<T>(this DependencyObject obj) where T : DependencyObject
{
return obj.GetAncestors().OfType<T>().FirstOrDefault();
}
public static IEnumerable<DependencyObject> GetAncestors(this DependencyObject element)
{
do
{
yield return element;
element = VisualTreeHelper.GetParent(element);
} while (element != null);
}
}
Then, for every items which need to change style do the following:
<Button x:Name="DeleteButton" Content="Delete" helpers:ToolBarHelper.TrackParentPanel="True">
<Button.Style>
<Style BasedOn="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="helpers:ToolBarHelper.IsInOverflowPanel" Value="True">
<!-- The Overflow style setters -->
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
I am creating a custom blend behavior to act on a ListboxItem i.e.
public class DragtestBehaviour : Behavior<ListBoxItem>
{
public DragtestBehaviour()
{ // Insert code required on object creation below this point.
}
protected override void OnAttached()
{
base.OnAttached();
// Insert code that you would want run when the Behavior is attached to an object.
}
protected override void OnDetaching()
{
base.OnDetaching();
// Insert code that you would want run when the Behavior is removed from an object.
}
}
What I am not able to figure out is how do I attach it to the listboxitem?
Alternatively, do I have to style the Listbox item and attach it to Border or any other element in style? If this is true then do I have to also derive my behavior class from Border (i.e. Framework element)?
DragtestBehaviour : Behavior<Frameworkelement>
I have basically done what eran otzap suggested in his comment, and what is described in this article by Mark Smith. This allows to still use Blend Behaviors with its benefits (like AssociatedObject), but be more flexible about attaching it. It's a fusion of normal attached behavior with blend behavior.
For completeness sake, here's the relevant code as well.
Attach the behavior in ItemContainerStyle:
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="behaviors:DragDropBehavior.IsAttached" Value="True" />
</Style>
</ListBox.ItemContainerStyle>
Behavior
public class DragDropBehavior : Behavior<ListBoxItem>
{
// IsAttached
public static DependencyProperty IsAttachedProperty = DependencyProperty.RegisterAttached("IsAttached",typeof(bool), typeof(DragDropBehavior),new FrameworkPropertyMetadata(false, OnIsAttachedChanged));
public static bool GetIsAttached(DependencyObject o){return (bool)o.GetValue(IsAttachedProperty);}
public static void SetIsAttached(DependencyObject o, bool value){o.SetValue(IsAttachedProperty, value);}
// is called the same as when attached in Interaction.Behaviors tag
protected override void OnAttached()
{
base.OnAttached();
}
// manual attachement to listbox item
private static void OnIsAttachedChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var el = o as UIElement;
if (el != null)
{
var behColl = Interaction.GetBehaviors(el);
var existingBehavior = behColl.FirstOrDefault(b => b.GetType() == typeof(DragDropBehavior)) as DragDropBehavior;
if ((bool)e.NewValue == false && existingBehavior != null)
{
behColl.Remove(existingBehavior);
}
else if ((bool)e.NewValue == true && existingBehavior == null)
{
behColl.Add(new DragDropBehavior());
}
}
}
}