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};");
}
Related
I'm trying to get all the DataGrid Expanders to open and close using a open/close button like the example below. Problem is when I'm using a single boolean to bind to this will break when I'm opening/closing a single expander.
I'm using a ListCollectionView to set the GroupingDescription.
I guess I'm looking for a solution to somehow get the Expander to play nice?
View
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="{Binding Source={StaticResource proxy}, Path=Data.Expanded, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
<Expander.Header>
<StackPanel>
<!-- label -->
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
ViewModel
public bool Expanded
{
get { return _expanded; }
set { _expanded = value; OnPropertyChanged(); }
}
public ListCollectionView Items
{
get
{
return _items;
}
set
{
_items = value; OnPropertyChanged();
}
}
// logic
var items = new ListCollectionView(planninglist);
items.SortDescriptions.Add(new items.GroupDescriptions.Add(new PropertyGroupDescription("AanZet"));
items.IsLiveSorting = true;
Update
Fix based on SO answer suggested in the comments by #XAMlMAX.
public class ExpanderBehavior
{
public static readonly DependencyProperty IsExpandedProperty =
DependencyProperty.RegisterAttached("IsExpanded",
typeof(bool),
typeof(ExpanderBehavior),
new PropertyMetadata(OnChanged));
public static bool GetIsExpanded(DependencyObject obj)
{
return (bool)obj.GetValue(IsExpandedProperty);
}
public static void SetIsExpanded(DependencyObject obj, bool value)
{
obj.SetValue(IsExpandedProperty, value);
}
private static void OnChanged(DependencyObject o,
DependencyPropertyChangedEventArgs args)
{
Expander tb = o as Expander;
if (null != tb)
tb.IsExpanded = (bool)args.NewValue;
}
}
As per comment conversation.
Reason why you were experiencing issue with expander staying open even though it was bound is because when you use OneWay Binding and then click on the button it then becomes disconnected. To have a better idea on when it becomes disconnected use PresentationTraceSources.TraceLevel=High.
To overcome this, one can use an attached property to stop Binding from detaching.
Now this example is for ToggleButton but the logic should apply the same.
public class TBExtender
{
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached("IsChecked",
typeof(bool),
typeof(TBExtender),
new PropertyMetadata(OnChanged));
public static bool GetIsChecked(DependencyObject obj)
{
return (bool)obj.GetValue(IsCheckedProperty);
}
public static void SetIsChecked(DependencyObject obj, bool value)
{
obj.SetValue(IsCheckedProperty, value);
}
private static void OnChanged(DependencyObject o,
DependencyPropertyChangedEventArgs args)
{
ToggleButton tb = o as ToggleButton;
if (null != tb)
tb.IsChecked = (bool)args.NewValue;
}
}
Credit goes to alex-p
and wallstreet-programmer
for responses on this SO question.
in my applications all inputs in textboxes have to be done by a inputbox i wrote myself. so whenever a textbox is klicked, the inputbox pops up. it looks like this:
the textbox in xaml:
<TextBox Text="{Binding Level}" Grid.Row="1" Grid.Column="2" Margin="2">
<TextBox.InputBindings>
<MouseBinding Command="{Binding EnterLevel}" MouseAction="LeftClick" />
</TextBox.InputBindings>
</TextBox>
the command in the VM:
private void ExecuteEnterLevel()
{
var dialog = new BFH.InputBox.InputBox("Level", 1, 1, 4);
dialog.ShowDialog();
Level = Convert.ToInt16(dialog.Input);
}
so the result of the inputbox becomes the text of the inputbox. this works fine.
now my question is: can i do that for all of my textboxes that need that functionality without coding an event for every single textbox? i would like to have "myTextbox" which does it automatically.
what i tried so far:
my textbox:
class MyTextbox : TextBox
{
public MyTextbox()
{
this.MouseDown += MyTextbox_MouseDown;
}
private void MyTextbox_MouseDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
TextBox tb = (TextBox)sender;
var dialog = new BFH.InputBox.InputBox("titel", "input");
dialog.ShowDialog();
tb.Text = dialog.Input;
}
}
and in xaml:
<libraries:MyTextbox/>
but MyTextbox_MouseDown is never executet. i put MessageBox.Show("test") in it without any results. i am doing something wrong i guess.
Since it is not possible to define InputBindings in styles, at least in a straight forward way. I offer to solve the issue by using AttachedProperties
lets start by defining a class for the attached property
namespace CSharpWPF
{
class EventHelper
{
public static ICommand GetLeftClick(DependencyObject obj)
{
return (ICommand)obj.GetValue(LeftClickProperty);
}
public static void SetLeftClick(DependencyObject obj, ICommand value)
{
obj.SetValue(LeftClickProperty, value);
}
// Using a DependencyProperty as the backing store for LeftClick. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LeftClickProperty =
DependencyProperty.RegisterAttached("LeftClick", typeof(ICommand), typeof(EventHelper), new PropertyMetadata(null, OnLeftClickChanged));
private static void OnLeftClickChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement elem = d as FrameworkElement;
ICommand command = e.NewValue as ICommand;
if (command != null)
elem.InputBindings.Add(new MouseBinding(command, new MouseGesture(MouseAction.LeftClick)));
}
}
}
this is basically translation of mouse binding in your code done on property changed.
usage
<TextBox l:EventHelper.LeftClick="{Binding MyCommand}" />
if you wish to apply to all TextBoxes then wrap the same in a generic style targeted to TextBox
<Style TargetType="TextBox">
<Setter Property="l:EventHelper.LeftClick"
Value="{Binding MyCommand}" />
</Style>
l: in the above refers to the name space of the above declared class eg xmlns:l="clr-namespace:CSharpWPF"
a full example
<StackPanel xmlns:l="clr-namespace:CSharpWPF">
<StackPanel.Resources>
<Style TargetType="TextBox">
<Setter Property="l:EventHelper.LeftClick"
Value="{Binding MyCommand}" />
</Style>
</StackPanel.Resources>
<TextBox />
<TextBox />
<TextBox />
<TextBox />
</StackPanel>
Attaching input box behavior
class
class InputHelper
{
public static bool GetIsInputBoxEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsInputBoxEnabledProperty);
}
public static void SetIsInputBoxEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsInputBoxEnabledProperty, value);
}
// Using a DependencyProperty as the backing store for IsInputBoxEnabled. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsInputBoxEnabledProperty =
DependencyProperty.RegisterAttached("IsInputBoxEnabled", typeof(bool), typeof(InputHelper), new PropertyMetadata(false, OnIsInputBoxEnabled));
private static void OnIsInputBoxEnabled(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBox tb = d as TextBox;
if ((bool)e.NewValue)
tb.PreviewMouseDown += elem_MouseDown;
else
tb.PreviewMouseDown -= elem_MouseDown;
}
static void elem_MouseDown(object sender, MouseButtonEventArgs e)
{
TextBox tb = sender as TextBox;
var dialog = new BFH.InputBox.InputBox(tb.GetValue(InputBoxTitleProperty), tb.GetValue(InputProperty));
dialog.ShowDialog();
tb.Text = dialog.Input;
}
public static string GetInputBoxTitle(DependencyObject obj)
{
return (string)obj.GetValue(InputBoxTitleProperty);
}
public static void SetInputBoxTitle(DependencyObject obj, string value)
{
obj.SetValue(InputBoxTitleProperty, value);
}
// Using a DependencyProperty as the backing store for InputBoxTitle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty InputBoxTitleProperty =
DependencyProperty.RegisterAttached("InputBoxTitle", typeof(string), typeof(InputHelper), new PropertyMetadata(null));
public static string GetInput(DependencyObject obj)
{
return (string)obj.GetValue(InputProperty);
}
public static void SetInput(DependencyObject obj, string value)
{
obj.SetValue(InputProperty, value);
}
// Using a DependencyProperty as the backing store for Input. This enables animation, styling, binding, etc...
public static readonly DependencyProperty InputProperty =
DependencyProperty.RegisterAttached("Input", typeof(string), typeof(InputHelper), new PropertyMetadata(null));
}
usage
<TextBox l:InputHelper.IsInputBoxEnabled="true"
l:InputHelper.InputBoxTitle="Title"
l:InputHelper.Input="Input" />
or via styles
<Style TargetType="TextBox">
<Setter Property="l:InputHelper.IsInputBoxEnabled"
Value="true" />
<Setter Property="l:InputHelper.Title"
Value="true" />
<Setter Property="l:InputHelper.Input"
Value="Input" />
</Style>
using attached properties will enable you to attach this behavior to existing classes instead of inheriting from them.
if you are inheriting the text box class you can add two properties to the same
eg
class MyTextbox : TextBox
{
public MyTextbox()
{
this.PreviewMouseDown += MyTextbox_MouseDown;
}
private void MyTextbox_MouseDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
TextBox tb = (TextBox)sender;
var dialog = new BFH.InputBox.InputBox(InputBoxTitle, Input);
dialog.ShowDialog();
tb.Text = dialog.Input;
}
public string InputBoxTitle { get; set; }
public string Input { get; set; }
}
usage
<libraries:MyTextbox InputBoxTitle="Title"
Input="Input" />
I have a Button containing a Hyperlink, like so:
<Button IsEnabled="False">
<Hyperlink IsEnabled="True">Testing</Hyperlink>
</Button>
I need the Hyperlink to be enabled, however the Button to be disabled. How can I achieve this?
The above simply results in both controls being disabled.
I solved this problem by creating a simple wrapper element that breaks the IsEnabled inheritance chain from the parent.
The framework's default coerce callback checks the parent IsEnabled value and inherits it. This control sets a new coerce callback that just returns the value directly without checking inheritance.
public class ResetIsEnabled : ContentControl
{
static ResetIsEnabled()
{
IsEnabledProperty.OverrideMetadata(
typeof(ResetIsEnabled),
new UIPropertyMetadata(
defaultValue: true,
propertyChangedCallback: (_, __) => { },
coerceValueCallback: (_, x) => x));
}
}
In the example from the question it would be used like this:
<Button IsEnabled="False">
<ResetIsEnabled>
<!-- Child elements within ResetIsEnabled have IsEnabled set to true (the default value) -->
<Hyperlink>Testing</Hyperlink>
</ResetIsEnabled>
</Button>
Control Hyperlink has strangely with the property IsEnabled. In addition to the one that you mentioned, namely, the full value inheritance from a parent, there is another similar.
Hyperlink for the specific control, which has been turned off (IsEnabled="False"), setting (IsEnabled="True") will not update the Hyperlink property. The solution - use a relative source for Hyperlink (more info).
For solving your question, I have decided that it is not the standard way to solve. So I created a Class with its own dependencies properties. It has it's property MyIsEnabled and MyStyle. As you might guess from the title, the first sets its property IsEnabled and MyStyle need to specify the button style, simulating the IsEnabled="False" behavior.
SimulateDisable Style
<Style x:Key="SimulateDisable" TargetType="{x:Type Button}">
<Setter Property="Opacity" Value="0.5" />
<Setter Property="Background" Value="Gainsboro" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border CornerRadius="4" BorderThickness="1" BorderBrush="DarkBlue" SnapsToDevicePixels="True">
<ContentPresenter x:Name="MyContentPresenter" Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Define Button with yours properties:
<Button Name="MyButton" local:MyClass.MyIsEnabled="False" local:MyClass.MyStyle="{StaticResource SimulateDisable}" Width="100" Height="30" Click="Button_Click">
<Hyperlink IsEnabled="True" Click="Hyperlink_Click">Testing</Hyperlink>
</Button>
Listing of MyClass
public class MyClass : DependencyObject
{
public static readonly DependencyProperty MyIsEnabledProperty;
public static readonly DependencyProperty MyStyleProperty;
#region MyIsEnabled
public static void SetMyIsEnabled(DependencyObject DepObject, bool value)
{
DepObject.SetValue(MyIsEnabledProperty, value);
}
public static bool GetMyIsEnabled(DependencyObject DepObject)
{
return (bool)DepObject.GetValue(MyIsEnabledProperty);
}
#endregion MyIsEnabled
#region MyStyle
public static void SetMyStyle(DependencyObject DepObject, Style value)
{
DepObject.SetValue(MyStyleProperty, value);
}
public static Style GetMyStyle(DependencyObject DepObject)
{
return (Style)DepObject.GetValue(MyStyleProperty);
}
#endregion MyStyle
static MyClass()
{
MyIsEnabledProperty = DependencyProperty.RegisterAttached("MyIsEnabled",
typeof(bool),
typeof(MyClass),
new UIPropertyMetadata(false, OnPropertyChanged));
MyStyleProperty = DependencyProperty.RegisterAttached("MyStyle",
typeof(Style),
typeof(MyClass),
new UIPropertyMetadata(OnPropertyChanged));
}
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Button MyButton = sender as Button;
bool MyBool = GetMyIsEnabled(MyButton);
if (MyBool == false)
{
MyButton.Style = MyClass.GetMyStyle(MyButton);
}
}
}
Plus for the event Hyperlink pointing e.Handled = true, so that the event did not happen next.
private void Hyperlink_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Hyperlink Click!");
e.Handled = true;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Button Click! Don't show it's!");
}
Output
P.S. Sorry for late answer :).
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".
I want to create a Style for a WPF ListBox that includes a Button in the ControlTemplate that the user can click on and it clears the ListBox selection.
I dont want to use codebehind so that this Style can be applied to any ListBox.
I have tried using EventTriggers and Storyboards and it has proved problematic as it only works first time and stopping the Storyboard sets the previous selection back.
I know I could use a user control but I want to know if it is possible to achieve this using only a Style.
It is not possible to achieve this using XAML and only the classes provided by the .NET framework. However you can still produce a reusable solution by defining a new command (call it ClearSelectionCommand) and a new attached property (call it ClearSelectionOnCommand).
Then you can incorporate those elements into your style.
Example:
public class SelectorBehavior
{
public static RoutedCommand
ClearSelectionCommand =
new RoutedCommand(
"ClearSelectionCommand",
typeof(SelectorBehavior));
public static bool GetClearSelectionOnCommand(DependencyObject obj)
{
return (bool)obj.GetValue(ClearSelectionOnCommandProperty);
}
public static void SetClearSelectionOnCommand(
DependencyObject obj,
bool value)
{
obj.SetValue(ClearSelectionOnCommandProperty, value);
}
public static readonly DependencyProperty ClearSelectionOnCommandProperty =
DependencyProperty.RegisterAttached(
"ClearSelectionOnCommand",
typeof(bool),
typeof(SelectorBehavior),
new UIPropertyMetadata(false, OnClearSelectionOnCommandChanged));
public static void OnClearSelectionOnCommandChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
Selector selector = d as Selector;
if (selector == null) return;
bool nv = (bool)e.NewValue, ov = (bool)e.OldValue;
if (nv == ov) return;
if (nv)
{
selector.CommandBindings.Add(
new CommandBinding(
ClearSelectionCommand,
ClearSelectionCommand_Executed,
ClearSelectionCommand_CanExecute));
}
else
{
var cmd = selector
.CommandBindings
.Cast<CommandBinding>()
.SingleOrDefault(x =>
x.Command == ClearSelectionCommand);
if (cmd != null)
selector.CommandBindings.Remove(cmd);
}
}
public static void ClearSelectionCommand_Executed(
object sender,
ExecutedRoutedEventArgs e)
{
Selector selector = (Selector)sender;
selector.SelectedIndex = -1;
}
public static void ClearSelectionCommand_CanExecute(
object sender,
CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
}
Example usage - XAML:
<Window x:Class="ClearSelectionBehaviorLibrary.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ClearSelectionBehaviorLibrary"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style x:Key="MyStyle" TargetType="Selector">
<Setter
Property="local:SelectorBehavior.ClearSelectionOnCommand"
Value="True"/>
</Style>
</Window.Resources>
<Grid>
<DockPanel>
<Button
DockPanel.Dock="Bottom"
Content="Clear"
Command="{x:Static local:SelectorBehavior.ClearSelectionCommand}"
CommandTarget="{Binding ElementName=TheListBox}"/>
<ListBox
Name="TheListBox"
ItemsSource="{Binding MyData}"
Style="{StaticResource MyStyle}"/>
</DockPanel>
</Grid>
</Window>
Example usage - Code Behind:
public partial class Window1 : Window
{
public List<string> MyData { get; set; }
public Window1()
{
MyData = new List<string>
{
"aa","bb","cc","dd","ee"
};
InitializeComponent();
DataContext = this;
}
}