In my application I have the following attached-property to call an ICommand when a UserControl or Window is loaded:
public static readonly DependencyProperty LoadedCommandProperty = DependencyProperty.RegisterAttached(
"LoadedCommand", typeof(ICommand), typeof(UserControlExtensions), new PropertyMetadata(null, OnLoadedCommandChanged));
private static void OnLoadedCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ContentControl userControl = d as ContentControl;
if (userControl == null)
return;
if (e.NewValue is ICommand)
{
userControl.Loaded += UserControlOnLoaded;
}
}
private static void UserControlOnLoaded(object sender, RoutedEventArgs e)
{
ContentControl userControl = sender as ContentControl;
if (userControl == null)
return;
ICommand command = GetLoadedCommand(userControl);
command.Execute(GetLoadedCommandParameter(userControl));
}
public static void SetLoadedCommand(DependencyObject element, ICommand value)
{
element.SetValue(LoadedCommandProperty, value);
}
public static ICommand GetLoadedCommand(DependencyObject element)
{
return (ICommand) element.GetValue(LoadedCommandProperty);
}
public static readonly DependencyProperty LoadedCommandParameterProperty = DependencyProperty.RegisterAttached(
"LoadedCommandParameter", typeof(object), typeof(UserControlExtensions), new PropertyMetadata(default(object)));
public static void SetLoadedCommandParameter(DependencyObject element, object value)
{
element.SetValue(LoadedCommandParameterProperty, value);
}
public static object GetLoadedCommandParameter(DependencyObject element)
{
return (object) element.GetValue(LoadedCommandParameterProperty);
}
This just works fine. In my views I call this on a UserControl like:
AttachedProperties:UserControlExtensions.LoadedCommand="{Binding ViewLoadedCommand}"
And the ICommand in the viewmodel will be called.
Now I've implemented a feature that I can click on a Button in the header of a TabItem and open the content of this TabItem in a new Window. (Like undocking a Tab in the visual studio).
Therefor I'm using the following code:
private void OpenInWindowButtonOnClick(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
if (button == null)
return;
TabItem tabItem = button.Tag as TabItem;
if (tabItem == null)
return;
string title = string.Empty;
ContentControl headerContent = tabItem.Header as ContentControl;
if (headerContent != null)
{
title = headerContent.Content.ToString();
}
string workspaceId = tabItem.Tag as string;
TabItem workspaceTab = WorkspaceTab.Items.OfType<TabItem>()
.FirstOrDefault(ti => ti.Tag is string && (string)ti.Tag == workspaceId);
if (workspaceTab != null)
{
WorkspaceWindow workspaceWindow = new WorkspaceWindow(tabItem);
workspaceWindow.Content = workspaceTab.Content;
workspaceWindow.Width = (workspaceTab.Content as FrameworkElement).ActualWidth;
workspaceWindow.Height = (workspaceTab.Content as FrameworkElement).ActualHeight;
workspaceWindow.Title = title;
workspaceWindow.Closing += WorkspaceWindowOnClosing;
workspaceWindow.Show();
workspaceWindows.Add(workspaceId, workspaceWindow);
WorkspaceTab.Items.Remove(workspaceTab);
}
}
This also just works fine.
My problem now is if I open a new tab (witch contains also a TabControl where the TabItems have the loaded-attachedproperty) and move this tab to a new window with the code above: The loaded-command will not called when I switch to the view where the loaded-attachedproperty is used.
If I debug the attachedproperty I can see, that the OnLoadedCommandChanged is called correctly but the UserControlOnLoaded is not called. If I don't open the TabItem in a new Window the UserControlOnLoaded is called correctly.
Any ideas why the loaded-event of subpages don't get fired if I move a TabItem to a new Window?
The problem was within the line WorkspaceTab.Items.Remove(workspaceTab);
This line removes the TabItem from the TabControl and it seems that wpf does here some kind of cleanup.
I've solved the problem be just setting the Visibility of the TabItem to Hidden.
Related
I am using a set of standard buttons, reused across a WPF app and want to add a shortcut key to each of the buttons.
So I have a ControlTemplate containing the buttons with Command bound to standard commands
I am trying to add a KeyBinding to the UserControl that contains the Button by adding Dependency Property to the Button and navigating up the Parent tree to the nearest UserControl.
I can get the DP values to set Key and Modifiers from the template Button, but cannot get hold of the Command (maybe because it is not bound until later??)
Any ideas how I can:
Either get hold of or create the Command from the Template per this approach
Or get the Command when it is resolved and then set the KeyBinding
PS: I have set the Key and Modifiers in separate DPs but would prefer to have a single DP of KeyBinding, then set ShortcutBinding.Key and ShortcutBinding.Modifers in XAML.
Is there a way to set the properties of a DP class in XAML like that?
Extract of the XAML of from the button group template:
<ctrl:ButtonShortcut
x:Name="btnUpdate"
Style="{StaticResource EditButtonStyle}"
Command="{Binding DataContext.UpdateCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Content="Update"
ShortcutKey="U"
ShortcutModifiers="Ctrl"/>
The DP class, inherited from Button, implementing the Key and Modifiers to link with the same Command that is bound to the Button:
public partial class ButtonShortcut : Button
{
public KeyBinding ShortcutBinding { get; set; }
public Key ShortcutKey
{
get { return (Key)GetValue(ShortcutKeyProperty); }
set { SetValue(ShortcutKeyProperty, value); }
}
public static readonly DependencyProperty ShortcutKeyProperty =
DependencyProperty.Register("ShortcutKey", typeof(Key), typeof(ButtonShortcut), new PropertyMetadata(Key.None, ShortcutKeyChanged));
public ModifierKeys ShortcutModifiers
{
get { return (ModifierKeys)GetValue(ShortcutModifiersProperty); }
set { SetValue(ShortcutModifiersProperty, value); }
}
public static readonly DependencyProperty ShortcutModifiersProperty =
DependencyProperty.Register("ShortcutModifiers", typeof(ModifierKeys), typeof(ButtonShortcut), new PropertyMetadata(ModifierKeys.None, ShortcutKeyChanged));
private static void ShortcutKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var btn = d as ButtonShortcut;
if (btn != null)
{
FrameworkElement uc = btn.Parent as FrameworkElement;
while (uc?.Parent != null && uc is not UserControl)
uc = uc.Parent as FrameworkElement;
if (btn.ShortcutBinding == null)
{
btn.ShortcutBinding = new KeyBinding();
}
var bindings = btn.CommandBindings;
if (e.NewValue is Key)
btn.ShortcutBinding.Key = (Key)e.NewValue;
if (e.NewValue is ModifierKeys)
btn.ShortcutBinding.Modifiers = (ModifierKeys)e.NewValue;
//So far, so good, but I cannot find the Command to apply to the KeyBinding
btn.ShortcutBinding.Command = btn.Command;
btn.ShortcutBinding.CommandParameter = btn.CommandParameter;
if (btn.Command == null)
System.Diagnostics.Debug.Print("not in Commmand");
if (btn.CommandBindings.Count == 0)
System.Diagnostics.Debug.Print("not in CommandBindings");
if (btn.ReadLocalValue(CommandProperty) == DependencyProperty.UnsetValue)
System.Diagnostics.Debug.Print("not in DP CommandProperty");
if (btn.ShortcutBinding.Key != Key.None && uc != null)
{
if (!uc.InputBindings.Contains(btn.ShortcutBinding))
uc.InputBindings.Add(btn.ShortcutBinding);
}
}
}
public ButtonShortcut()
{
InitializeComponent();
}
}
This looks like a very bad smelling design. The child control should not configure the parent control. Especially not to add "features" that the child pretends to offer: in your case the button is not able to execute key gestures - it only defines them.
Since the parent control is the target of the gestures, it must define the required handlers to execute the gesture. This means, all the action and responsibilities are in the parent UserControl and not in the Button (or child element). Classes should never know details of other classes. A button should never know which parent will execute its command and how to register the command in the first place.
The button is just a passive source. It does not handle commands or gestures itself. Therefore, it would never register a CommandBinding or InputBinding.
Usually, you bind the Button.Command to a command defined on the command target. The button invokes this command on the target (or raises a RoutedCommand) and the target executes a corresponding operation.
It doesn't make sense to define commands or key gestures on a button. The button won't execute them. Commands, key and mouse gestures must be defined on the target, where the related responsibilities are.
You can let the parent UserControl (which I assume in this example is the command target) define a RoutedCommand. Register the corresponding key gesture(s) with this command:
Solution 1
MyUserControl.xaml.cs
partial class MyUserControl : UserControl
{
public static RoutedCommand DoActionCommand { get; }
static MyUserControl()
{
var gestures = new InputGestureCollection
{
new KeyGesture(Key.U, ModifierKeys.Control),
};
DoActionCommand = new RoutedUICommand(
"Do something",
nameof(DoActionCommand),
typeof(CommandTargetUserControl),
gestures);
}
public MyUserControl()
{
InitializeComponent();
this.CommandBindings.Add(new CommandBinding(DoActionCommand, ExecuteDoActionCommand));
}
}
CommandTargetUserControl.xaml
<MyUserControl>
<Button Command="{x:Static local:CommandTargetUserControl.DoActionCommand}" />
</MyUserControl>
Solution 2
To allow configuring default key gestures without modifying the source and the target, you can implement an attached behavior. This behavior basically delegates a hooked command, which is invoked by a key gesture, to the actual command sources that are registered with the particular key gesture. Command source and command target are totally decoupled. Input bindings are configured explicitly to respect encapsulation (no silent and unexpected modidifications).
Usage example
<Window local:KeyGestureDelegate.IsKeyGestureDelegationEnabled="True">
<local:KeyGestureDelegate.TargetKeyGestures>
<InputGestureCollection>
<KeyGesture>Ctrl+U</KeyGesture>
<KeyGesture>Ctrl+Shift+M</KeyGesture>
</InputGestureCollection>
</local:KeyGestureDelegate.TargetKeyGestures>
<StackPanel>
<Button Command="{Binding SomeCommand}"
local:KeyGestureDelegate.SourceKeyGesture="Ctrl+U"
local:KeyGestureDelegate.IsKeyGestureCommandExecutionEnabled="True" />
<Button Command="{Binding SomeOtherCommand}"
local:KeyGestureDelegate.SourceKeyGesture="Shift+Ctrl+M"
local:KeyGestureDelegate.IsKeyGestureCommandExecutionEnabled="True" />
</StackPanel>
</Window>
Implementation example
KeyGestureDelegate.cs
An implementation of the RelayCommand used in this example can be found at Microsoft Docs: Relaying Command Logic.
public class KeyGestureDelegate : DependencyObject
{
// Custom KeyGesture comparer for the Dictionary
private class KeyGestureComparer : EqualityComparer<KeyGesture>
{
public override bool Equals(KeyGesture? x, KeyGesture? y)
=> (x?.Key, x?.Modifiers).Equals((y?.Key, y?.Modifiers));
public override int GetHashCode([DisallowNull] KeyGesture obj)
=> HashCode.Combine(obj.Key, obj.Modifiers);
}
private static ICommand KeyGestureDelegateCommand { get; } = new RelayCommand(ExecuteKeyGestureDelegateCommand);
public static bool GetIsKeyGestureDelegationEnabled(DependencyObject attachedElement) => (bool)attachedElement.GetValue(IsKeyGestureDelegationEnabledProperty);
public static void SetIsKeyGestureDelegationEnabled(DependencyObject attachedElement, bool value) => attachedElement.SetValue(IsKeyGestureDelegationEnabledProperty, value);
public static readonly DependencyProperty IsKeyGestureDelegationEnabledProperty = DependencyProperty.RegisterAttached(
"IsKeyGestureDelegationEnabled",
typeof(bool),
typeof(KeyGestureDelegate),
new PropertyMetadata(default(bool), OnIsKeyGestureDelegationEnabled));
public static bool GetIsKeyGestureCommandExecutionEnabled(DependencyObject attachedElement) => (bool)attachedElement.GetValue(IsKeyGestureCommandExecutionEnabledProperty);
public static void SetIsKeyGestureCommandExecutionEnabled(DependencyObject attachedElement, bool value) => attachedElement.SetValue(IsKeyGestureCommandExecutionEnabledProperty, value);
public static readonly DependencyProperty IsKeyGestureCommandExecutionEnabledProperty = DependencyProperty.RegisterAttached(
"IsKeyGestureCommandExecutionEnabled",
typeof(bool),
typeof(KeyGestureDelegate),
new PropertyMetadata(default(bool), OnIsKeyGestureCommandExecutionEnabled));
public static InputGestureCollection GetTargetKeyGestures(DependencyObject obj) => (InputGestureCollection)obj.GetValue(TargetKeyGesturesProperty);
public static void SetTargetKeyGestures(DependencyObject obj, InputGestureCollection value) => obj.SetValue(TargetKeyGesturesProperty, value);
public static readonly DependencyProperty TargetKeyGesturesProperty = DependencyProperty.RegisterAttached(
"TargetKeyGestures",
typeof(InputGestureCollection),
typeof(KeyGestureDelegate),
new PropertyMetadata(default(InputGestureCollection), OnTargetKeyGesturesChanged));
public static KeyGesture GetSourceKeyGesture(DependencyObject attachedElement) => (KeyGesture)attachedElement.GetValue(SourceKeyGestureProperty);
public static void SetSourceKeyGesture(DependencyObject attachedElement, KeyGesture value) => attachedElement.SetValue(SourceKeyGestureProperty, value);
public static readonly DependencyProperty SourceKeyGestureProperty = DependencyProperty.RegisterAttached(
"SourceKeyGesture",
typeof(KeyGesture),
typeof(KeyGestureDelegate),
new PropertyMetadata(default(KeyGesture), OnSourceKeyGestureChanged));
// Remember added InputBindings to enable later removal
private static Dictionary<UIElement, IList<InputBinding>> InputBindingTargetMap { get; } = new Dictionary<UIElement, IList<InputBinding>>();
// Lookup command sources that map to a particular gesture
private static Dictionary<KeyGesture, IList<ICommandSource>> InputBindingSourceMap { get; } = new Dictionary<KeyGesture, IList<ICommandSource>>(new KeyGestureComparer());
private static void OnIsKeyGestureDelegationEnabled(DependencyObject attachedElement, DependencyPropertyChangedEventArgs e)
{
if (attachedElement is not UIElement keyGestureHandler)
{
throw new ArgumentException($"Attached element must be of type {typeof(UIElement)}.");
}
InputGestureCollection gestures = GetTargetKeyGestures(keyGestureHandler);
if ((bool)e.NewValue)
{
RegisterKeyBinding(keyGestureHandler, gestures);
}
else
{
UnregisterKeyBinding(keyGestureHandler);
}
}
private static void OnIsKeyGestureCommandExecutionEnabled(DependencyObject attachedElement, DependencyPropertyChangedEventArgs e)
{
if (attachedElement is not ICommandSource commandSource)
{
throw new ArgumentException($"Attached element must be of type {typeof(ICommandSource)}.");
}
KeyGesture keyGesture = GetSourceKeyGesture(attachedElement);
if ((bool)e.NewValue)
{
RegisterCommandBinding(commandSource, keyGesture);
}
else
{
UnregisterCommandBinding(commandSource, keyGesture);
}
}
private static void OnTargetKeyGesturesChanged(DependencyObject attachedElement, DependencyPropertyChangedEventArgs e)
{
if (attachedElement is not UIElement keyGestureHandler)
{
throw new ArgumentException($"Attached element must be of type {typeof(UIElement)}.");
}
if (e.OldValue is InputBindingCollection)
{
UnregisterKeyBinding(keyGestureHandler);
}
if (!GetIsKeyGestureDelegationEnabled(keyGestureHandler))
{
return;
}
RegisterKeyBinding(keyGestureHandler, e.NewValue as InputGestureCollection);
}
private static void OnSourceKeyGestureChanged(DependencyObject attachedElement, DependencyPropertyChangedEventArgs e)
{
if (attachedElement is not ICommandSource commandSource)
{
throw new ArgumentException($"Attached element must be of type {typeof(ICommandSource)}.");
}
UnregisterCommandBinding(commandSource, e.OldValue as KeyGesture);
if (!GetIsKeyGestureCommandExecutionEnabled(attachedElement))
{
return;
}
RegisterCommandBinding(commandSource, e.NewValue as KeyGesture);
}
private static void ExecuteKeyGestureDelegateCommand(object commandParameter)
{
if (InputBindingSourceMap.TryGetValue(commandParameter as KeyGesture, out IList<ICommandSource> commandSources))
{
foreach (ICommandSource commandSource in commandSources)
{
ExecuteCommandSource(commandSource);
}
}
}
private static void ExecuteCommandSource(ICommandSource commandSource)
{
if (commandSource.Command is RoutedCommand routedCommand)
{
IInputElement commandTarget = commandSource.CommandTarget ?? commandSource as IInputElement;
if (routedCommand.CanExecute(commandSource.CommandParameter, commandTarget))
{
routedCommand.Execute(commandSource.CommandParameter, commandTarget);
}
}
else if (commandSource.Command?.CanExecute(parameter: commandSource.CommandParameter) ?? false)
{
commandSource.Command.Execute(commandSource.CommandParameter);
}
}
private static void RegisterKeyBinding(UIElement keyGestureHandler, InputGestureCollection inputGestureCollection)
{
if (inputGestureCollection == null)
{
return;
}
IList<InputBinding>? inputBindings = new List<InputBinding>();
InputBindingTargetMap.Add(keyGestureHandler, inputBindings);
foreach (KeyGesture gesture in inputGestureCollection.OfType<KeyGesture>())
{
var inputBinding = new KeyBinding(KeyGestureDelegateCommand, gesture) { CommandParameter = gesture };
keyGestureHandler.InputBindings.Add(inputBinding);
inputBindings.Add(inputBinding);
}
}
private static void UnregisterKeyBinding(UIElement keyGestureHandler)
{
if (InputBindingTargetMap.TryGetValue(keyGestureHandler, out IList<InputBinding>? inputBindings))
{
foreach (InputBinding inputBinding in inputBindings)
{
keyGestureHandler.InputBindings.Remove(inputBinding);
}
InputBindingTargetMap.Remove(keyGestureHandler);
}
}
private static void RegisterCommandBinding(ICommandSource commandSource, KeyGesture keyGesture)
{
if (keyGesture == null)
{
return;
}
if (!InputBindingSourceMap.TryGetValue(keyGesture, out IList<ICommandSource>? commandSources))
{
commandSources = new List<ICommandSource>();
InputBindingSourceMap.Add(keyGesture, commandSources);
}
commandSources.Add(commandSource);
}
private static void UnregisterCommandBinding(ICommandSource commandSource, KeyGesture keyGesture)
{
if (keyGesture == null)
{
return;
}
if (InputBindingSourceMap.TryGetValue(keyGesture, out IList<ICommandSource>? commandSources))
{
commandSources.Remove(commandSource);
if (!commandSources.Any())
{
InputBindingSourceMap.Remove(keyGesture);
}
}
}
}
I have a custom control that has a DependencyProperty of type ObservableCollection that is bound to an observableCollection:
<MyControl MyCollectionProperty = {Binding MyObservableCollection} ...
Problem is adding to MyObservableCollection does not update MyCollectionProperty.
I need to completly replace the MyObservableCollection to make it work e.g.
MyObservableCollection = null;
MyObservableCollection = new ObservableCollection(){...}
Is there a better way to deal with this?
EDIT:
public ObservableCollection<string> Columns
{
get { return (ObservableCollection<string>)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(ObservableCollection<string>), typeof(MyControl),
new PropertyMetadata(new ObservableCollection<string>(), OnChanged));
In addition to what grantz has answered, I would suggest to declare the property with type IEnumerable<string> and check at runtime if the collection object implements the INotifyCollectionChanged interface. This provides greater flexibility as to which concrete collection implementation may be used as property value. A user may then decide to have their own specialized implementation of an observable collection.
Note also that in the ColumnsPropertyChanged callback the CollectionChanged event handler is attached to the new collection, but also removed from the old one.
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(
"Columns", typeof(IEnumerable<string>), typeof(MyControl),
new PropertyMetadata(null, ColumnsPropertyChanged));
public IEnumerable<string> Columns
{
get { return (IEnumerable<string>)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
private static void ColumnsPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var control= (MyControl)obj;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= control.ColumnsCollectionChanged;
}
if (newCollection != null)
{
newCollection.CollectionChanged += control.ColumnsCollectionChanged;
}
control.UpdateColumns();
}
private void ColumnsCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
// optionally take e.Action into account
UpdateColumns();
}
private void UpdateColumns()
{
...
}
Below is a working example that may help.
In this example, the method OnChanged is called immediately, when the Add button is clicked "Changed" is written to the console.
The Control
public class MyControl : Control
{
public ObservableCollection<string> ExtraColumns
{
get { return (ObservableCollection<string>)GetValue(ExtraColumnsProperty); }
set { SetValue(ExtraColumnsProperty, value); }
}
public static readonly DependencyProperty ExtraColumnsProperty =
DependencyProperty.Register("ExtraColumns", typeof(ObservableCollection<string>), typeof(MyControl),
new PropertyMetadata(new ObservableCollection<string>(), OnChanged));
static void OnChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
(sender as MyControl).OnChanged();
}
void OnChanged()
{
if ( ExtraColumns != null )
ExtraColumns.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ExtraColumns_CollectionChanged);
}
void ExtraColumns_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Console.WriteLine("Changed");
}
}
The Window
<Window x:Class="WpfApplication18.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication18"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<local:MyControl ExtraColumns="{Binding Extras}"/>
<Button Click="Button_Click">Add</Button>
</StackPanel>
</Window>
Window Code Behind
public partial class MainWindow : Window
{
private ObservableCollection<string> _extras = new ObservableCollection<string>( );
public ObservableCollection<string> Extras
{
get { return _extras; }
set
{
if (value != _extras)
{
_extras = value;
}
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Extras.Add("Additional");
}
}
I am trying to dynamically hide the Toggle Button on an Expander using a Property on my ViewModel set to the Visibility property. At least that is my thought. Is there a way to just alter that one setting on the ToggleButton control without having to do the ENTIRE template of the Toggle Button in my xaml?
You could use the Loaded event for the Expander and set up the Binding in the event handler or if you want a reusable way without code behind you could use an attached behavior.
The attached behavior finds the ToggleButton inside the Template and sets up the Binding to the attached property ToggleButtonVisibility.
Uploaded a sample app here: ExpanderToggleButtonVisibilityTest.zip
Use it like this
<Expander Name="expander"
behaviors:ExpanderBehavior.BindToggleButtonVisibility="True"
behaviors:ExpanderBehavior.ToggleButtonVisibility="{Binding YourVisibilityProperty}"
.../>
ExpanderBehavior
public class ExpanderBehavior
{
public static DependencyProperty BindToggleButtonVisibilityProperty =
DependencyProperty.RegisterAttached("BindToggleButtonVisibility",
typeof(bool),
typeof(ExpanderBehavior),
new PropertyMetadata(false, OnBindToggleButtonVisibilityChanged));
public static bool GetBindToggleButtonVisibility(Expander expander)
{
return (bool)expander.GetValue(BindToggleButtonVisibilityProperty);
}
public static void SetBindToggleButtonVisibility(Expander expander, bool value)
{
expander.SetValue(BindToggleButtonVisibilityProperty, value);
}
private static void OnBindToggleButtonVisibilityChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
Expander expander = target as Expander;
if (expander.IsLoaded == true)
{
BindToggleButtonVisibility(expander);
}
else
{
RoutedEventHandler loadedEventHandler = null;
loadedEventHandler = new RoutedEventHandler(delegate
{
BindToggleButtonVisibility(expander);
expander.Loaded -= loadedEventHandler;
});
expander.Loaded += loadedEventHandler;
}
}
private static void BindToggleButtonVisibility(Expander expander)
{
ToggleButton headerSite = expander.Template.FindName("HeaderSite", expander) as ToggleButton;
if (headerSite != null)
{
Binding visibilityBinding = new Binding
{
Source = expander,
Path = new PropertyPath(ToggleButtonVisibilityProperty)
};
headerSite.SetBinding(ToggleButton.VisibilityProperty, visibilityBinding);
}
}
#region ToggleButtonVisibilityProperty
public static DependencyProperty ToggleButtonVisibilityProperty =
DependencyProperty.RegisterAttached("ToggleButtonVisibility",
typeof(Visibility),
typeof(ExpanderBehavior),
new PropertyMetadata(Visibility.Visible));
public static Visibility GetToggleButtonVisibility(Expander expander)
{
return (Visibility)expander.GetValue(ToggleButtonVisibilityProperty);
}
public static void SetToggleButtonVisibility(Expander expander, Visibility value)
{
expander.SetValue(ToggleButtonVisibilityProperty, value);
}
#endregion // ToggleButtonVisibilityProperty
}
I have following class that defines attached property to set children's margin:
public class MarginSetter
{
public static Thickness GetMargin(DependencyObject obj)
{
return (Thickness)obj.GetValue(MarginProperty);
}
public static void SetMargin(DependencyObject obj, Thickness value)
{
obj.SetValue(MarginProperty, value);
}
public static readonly DependencyProperty MarginProperty =
DependencyProperty.RegisterAttached("Margin", typeof(Thickness), typeof(MarginSetter), new UIPropertyMetadata(new Thickness(), CreateThicknesForChildren));
public static void CreateThicknesForChildren(object sender, DependencyPropertyChangedEventArgs e)
{
var panel = sender as Panel;
if (panel == null) return;
foreach (var child in panel.Children)
{
var fe = child as FrameworkElement;
if (fe == null) continue;
fe.Margin = MarginSetter.GetMargin(panel);
}
}
}
The problem is that when CreateThicknesForChildren is invoked no child controls are added to parent yet. How to fix this class so that it will correctly set margin on all child controls?
In my project no controls are dynamically added to parent, they all are created in xaml file. By the way, designer works correctly and somehow correctly sets margin for all children elements.
How about registering to the Loaded event of the panel? it won't help if you're adding items dynamically later, but for the basic 95% it would work:
public static void CreateThicknesForChildren(object sender, DependencyPropertyChangedEventArgs e)
{
var panel = sender as Panel;
panel.Loaded += ...
}
I used a those article to create AttachedProperty of Webbrowser control, so I can bind the “BindableSource” to my public property “Path”. It is great and work well with one small disadvantage.
In my ViewModel constructor the property is set to:
Path = new Uri("http://microsoft.com");
When the page is loaded I navigate to another link and “WebBrowser.Source” property is changed but “WebBrowser.BindableSource” is not changed.
When I click a button which set again:
Path = new Uri("http://microsoft.com");
The “BindableSourcePropertyChanged” is not called because the property has those value.
I had an idea how to solve it but its not good, because slow the performance, It is to set Path to another URL and after that set it to real URL (http://microsoft.com).
Path = new Uri("http://google.com");
Path = new Uri("http://microsoft.com");
Can you give me please a better idea, how can I solved it.
Thanks in advanced.
I propose to synchronize BindableSource with Navigated event.
You coukld achieve this by exposing another one attached behaviour at your WebBrowserUtility class, that reacts on Navigated event like this:
public static readonly DependencyProperty ShouldHandleNavigatedProperty =
DependencyProperty.RegisterAttached(
"ShouldHandleNavigated",
typeof(Boolean),
typeof(WebBrowserUtility),
new UIPropertyMetadata(false, ShouldHandleNavigatedPropertyChanged));
public static Boolean GetShouldHandleNavigated(DependencyObject obj)
{
return (Boolean)obj.GetValue(BindableSourceProperty);
}
public static void SetShouldHandleNavigated(DependencyObject obj, Boolean value)
{
obj.SetValue(ShouldHandleNavigatedProperty, value);
}
public static void ShouldHandleNavigatedPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
WebBrowser browser = o as WebBrowser;
if (browser != null)
{
if ((bool)e.NewValue)
{
browser.Navigated += new NavigatedEventHandler(Browser_Navigated);
}
else
{
browser.Navigated -= new NavigatedEventHandler(Browser_Navigated);
}
}
}
private static void Browser_Navigated(object sender, NavigationEventArgs e)
{
WebBrowser browser = sender as WebBrowser;
if (browser != null)
{
browser.SetValue(WebBrowserUtility.BindableSourceProperty, browser.Source.AbsoluteUri);
}
}
Usage in xaml:
<WebBrowser self:WebBrowserUtility.BindableSource="{Binding WebAddress}"
self:WebBrowserUtility.ShouldHandleNavigated="True"/>
P.S. I should admit that this implementation is rather dirty, because setting of BindableSource inside Navigated event handler forces one additional event fireing. But this code works, and you could consider about its improvement.
EDITED
public static class WebBrowserUtility
{
...
private const string _SkipSourceChange = "Skip";
public static void BindableSourcePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
WebBrowser browser = o as WebBrowser;
if (browser != null)
{
string uri = e.NewValue as string;
if (!_SkipSourceChange.Equals(browser.Tag))
{
browser.Source = uri != null ? new Uri(uri) : null;
}
}
}
private static void Browser_Navigated(object sender, NavigationEventArgs e)
{
WebBrowser browser = sender as WebBrowser;
if (browser != null)
{
if (WebBrowserUtility.GetBindableSource(browser) != e.Uri.ToString())
{
browser.Tag = _SkipSourceChange;
browser.SetValue(WebBrowserUtility.BindableSourceProperty, browser.Source.AbsoluteUri);
browser.Tag = null;
}
}
}
}