How to make attached property react to children collection change? - c#

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 += ...
}

Related

Open TabItem in a new Window

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.

Attached behaviour on parent mvvm doesn't work

I have 2 Usercontrols, parent and child, child control has button witch I want to click with parent viewmodel method, but it doesn't work, please tell me what I miss
in parent view, I have something like this:
XAML
...
<view:childUC vm:ChildBehaviuor.AddCommand="{Binding ExampleCommand}"/>
Behavior code:
public static readonly DependencyProperty AddCommandProperty =DependencyProperty.RegisterAttached
(
"AddCommand",
typeof(ICommand),
typeof(childBehavior),
new PropertyMetadata(OnAddCommand)
);
public static ICommand GetAddCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(AddCommandProperty);
}
public static void SetAddCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(AddCommandProperty,value);
}
private static ICommand command;
private static void OnAddCommand(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
child gp = sender as child;
childBehavior.command = (ICommand)sender.GetValue(childBehavior.AddCommandProperty);
if(gp != null && command != null)
{
if ((e.NewValue != null) && (e.OldValue == null))
{
gp.AddButton.Click += ButtonClick;
}
else if ((e.NewValue == null) && (e.OldValue != null))
{
gp.AddButton.Click -= ButtonClick;
}
}
}
public static void ButtonClick(object sender,RoutedEventArgs eventArgs)
{
childBehavior.command.Execute(null);
}
VM parent command:
public ICommand ExampleCommand
{
get
{
if (this.exampleCommand == null)
{
this.exampleCommand = new DelegateCommand(...);
}
return this.exampleCommand ;
}
}
I am not sure if I understood you but if you are looking for a way to execute a command on your child usercontrol when you click a button in your parent usercontrol you need to do following:
Let your parent usercontrol implement ICommandSource interface which contains a property called "Command".
Bind the certain command which is in your child usercontrol to the "Command" property which you will have available on your parent usercontrol after you implement ICommandSource interface.
When you click on your button which is in your parent usercontrol, access inside the button handler, which is a method in your parent usercontrol, the available Command propery that you got though the interface. After accessing the Command property call Command.Execute() method, which will go to your child usercontrol and trigger the command you binded before.
Thats how you execute an command on child usercontrol from parent usercontrol.. if you want it the other way around you just have to replace every child word with parent, and every parent word with child :)

Bind Data to Windows Form TabControl

I'm attempting my first Windows Form project, having been entirely web based previously and experiencing some issues. I want to bind a list of objects to a TabControl and have this create the Tabs and then have a databound value accessible from the click event of each tab.
The Object I'm wanting to bind is
public class TreeNodeItem
{
private NTree<string> node;
public TreeNodeItem(NTree<string> node)
{
this.node = node;
}
public string Value
{
get { return this.node.data; }
}
}
The NTree node represents a node in an object that models data in a tree structure. I want to create a tab for each object in the list with the Value property being bound to the Tab Text property. Other posts mention binding to the ItemsSource property of the control, but Visual Studio is not giving me this property.
Any help will be greatly appreciated.
Cheers
Stewart
Okay, I was unaware of that the binding was a must. Although I have never seen something like this being done in a Windows Forms Application, I've decided to create a class that does this for us.
It uses the ObservableCollection<T> to keep track whether an object / property has been changed inside its list.
public class ObservableList<T> : ObservableCollection<T>
{
public ObservableList() : base()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(nObservableCollection_CollectionChanged);
}
public event PropertyChangedEventHandler OnPropertyChanged;
void nObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (OnPropertyChanged != null)
{
OnPropertyChanged(new object[] { e.OldItems, e.NewItems }, null); // Call method to let it change the tabpages
}
}
}
Now, we have to create a helper class that helps us keeping track:
public class TabControlBind
{
public TabControlBind(TabControl tabControl)
{
// Create a new TabPageCollection and bind it to our tabcontrol
this._tabPages = new TabControl.TabPageCollection(tabControl);
}
// Fields
private ObservableList<TreeNodeItem> _treeNodeItems;
private TabControl.TabPageCollection _tabPages;
// Properties
public ObservableList<TreeNodeItem> TreeNodeItems
{
get { return _treeNodeItems; }
set
{
if (_treeNodeItems != value)
{
_treeNodeItems = value;
_treeNodeItems.OnPropertyChanged += OnPropretyChanged;
OnPropretyChanged(null, null);
}
}
}
public TabControl.TabPageCollection TabPages
{
get
{
return this._tabPages;
}
}
// Events
private void OnPropretyChanged(object sender, PropertyChangedEventArgs e)
{
if (sender == null) // If list got set
{
// Remove existing tabpages
this._tabPages.Clear();
// Loop through all items inside the ObservableList object and add them to the Tabpage
foreach (TreeNodeItem _treeNodeItem in this._treeNodeItems)
{
TabPage tabPage = new TabPage() { Text = _treeNodeItem.Value, Tag = _treeNodeItems };
this._tabPages.Add(tabPage);
}
}
else if (sender is object[]) // If only one (or multiple) objects have been changed
{
// Get OldItems and NewItems
object[] changedItems = (object[])sender;
// Remove OldItems
if (changedItems[0] != null)
{
foreach (dynamic oldItems in (IList)changedItems[0])
{
foreach (TabPage tab in this._tabPages)
{
if (tab.Text == oldItems.Value)
{
this._tabPages.Remove(tab);
break;
}
}
}
}
// Add OldItems
if (changedItems[1] != null)
{
foreach (dynamic newItems in (IList)changedItems[1])
{
TabPage tabPage = new TabPage() { Text = newItems.Value, Tag = newItems };
this._tabPages.Add(tabPage);
}
}
}
}
}
This is a sample on how to use it:
TabControlBind tabControlBinder;
ObservableList<TreeNodeItem> treeNodeItems;
private void btnAdd_Click(object sender, EventArgs e)
{
// This will automatically update the TabControl
treeNodeItems.Add(new TreeNodeItem(new NTree<string>() { data = "Test3" }));
}
private void frmMain_Load(object sender, EventArgs e)
{
// Create a new list object an add items to it
treeNodeItems = new ObservableList<TreeNodeItem>();
treeNodeItems.Add(new TreeNodeItem(new NTree<string>() { data = "Test" }));
treeNodeItems.Add(new TreeNodeItem(new NTree<string>() { data = "Test2" }));
// Create a new instance of the TabControlBind class, set it to our TabControl
tabControlBinder = new TabControlBind(tabControl);
tabControlBinder.TreeNodeItems = treeNodeItems;
}

Hide the ToggleButton on WPF Expander via Binding

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
}

How do you have a button in a property grid?

I have a property grid that will have a few properties referenced. I would like to have one of the items in the property grid to be a button or even have a ellipses button which will act like a button on a normal win form.
Is there a way to do this?
Appreciate your help in advance!
I recommend reading Getting the Most Out of the .NET Framework PropertyGrid Control.
It walks through how to create a custom UI for your property, which could include a button that opens a popup/separate form/etc.
I added collapse all and expand all buttons to the PropertyGrid using extension methods.
PropertyGrid Buttons
namespace MyNameSpace
{
public static class PropertyGridHelper
{
private static PropertyGrid getPropertyGridParent(object sender)
{
PropertyGrid propertyGrid = null;
ToolStripButton toolStripButton = sender as ToolStripButton;
// ToolStripButton -> ToolStrip -> PropertyGrid
if (toolStripButton != null)
{
ToolStrip toolStrip = toolStripButton.GetCurrentParent() as ToolStrip;
if (toolStrip != null)
{
propertyGrid = toolStrip.Parent as PropertyGrid;
if (propertyGrid != null)
{
propertyGrid.CollapseAllGridItems();
}
}
}
return propertyGrid;
}
private static void propertyGridCollapseAllClick(object sender, EventArgs e)
{
PropertyGrid propertyGrid = getPropertyGridParent(sender);
if (propertyGrid != null)
{
propertyGrid.CollapseAllGridItems();
}
}
private static void propertyGridExpandAllClick(object sender, EventArgs e)
{
PropertyGrid propertyGrid = getPropertyGridParent(sender);
if (propertyGrid != null)
{
propertyGrid.ExpandAllGridItems();
}
}
public static void AddCollapseExpandAllButtons(this System.Windows.Forms.PropertyGrid propertyGrid)
{
foreach (Control control in propertyGrid.Controls)
{
ToolStrip toolStrip = control as ToolStrip;
if (toolStrip != null)
{
toolStrip.Items.Add(new ToolStripButton("", Properties.Resources.CollapseAll, propertyGridCollapseAllClick));
toolStrip.Items.Add(new ToolStripButton("", Properties.Resources.ExpandAll, propertyGridExpandAllClick));
}
}
}
}
}
UITypeEditor, using the IWindowsFormsEditorService... thats what it was. Got it! Thanks for the direction!

Categories

Resources