I am trying to bind AvalonDock LayoutAnchorables to their respective menu items in WPF. If checked in the menu the anchorable should be visible. If not checked in the menu, the anchorable should be hidden.
Both IsChecked and IsVisible are boolean so I wouldn't expect a converter to be required. I can set the LayoutAnchorable IsVisible property to True or False, and behavior is as expected in the design view.
However, if trying to implement binding as below I get the error
'Binding' cannot be set on the 'IsVisible' property of type
'LayoutAnchorable'. A 'Binding' can only be set on a
DependencyProperty of a DependencyObject.
The problem is here:
<dock:LayoutAnchorable ContentId="content1" IsVisible="{Binding IsChecked, ElementName=mnuPane1}" x:Name="anchorable1" IsSelected="True">
How can I do this?
<Window x:Class="TestAvalonBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dock="http://schemas.xceed.com/wpf/xaml/avalondock"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Menu -->
<Menu Height="18" HorizontalAlignment="Stretch" Name="menu1" VerticalAlignment="Top" Grid.Row="0">
<MenuItem Header="File">
<MenuItem Header="_Foo1" Name="mnuPane1" IsCheckable="True">
</MenuItem>
<MenuItem Header="Foo2" Name="mnuPane2" IsCheckable="True">
</MenuItem>
</MenuItem>
</Menu>
<!-- AvalonDock -->
<dock:DockingManager x:Name="Dockman" DockPanel.Dock="Left" Grid.Row="1" >
<dock:LayoutRoot x:Name="_layoutRoot">
<dock:LayoutPanel Orientation="Horizontal">
<dock:LayoutAnchorablePaneGroup Orientation="Vertical">
<dock:LayoutAnchorablePane FloatingWidth="150" FloatingHeight="150" FloatingLeft="100" FloatingTop="300">
<dock:LayoutAnchorable ContentId="content1" IsVisible="{Binding IsChecked, ElementName=mnuPane1}" x:Name="anchorable1" IsSelected="True">
<GroupBox Header="Foo1"/>
</dock:LayoutAnchorable>
</dock:LayoutAnchorablePane>
<dock:LayoutAnchorablePane FloatingWidth="150" FloatingHeight="150" FloatingLeft="100" FloatingTop="300">
<dock:LayoutAnchorable ContentId="content2" x:Name="anchorable2" IsSelected="True">
<GroupBox Header="Foo2"/>
</dock:LayoutAnchorable>
</dock:LayoutAnchorablePane>
</dock:LayoutAnchorablePaneGroup>
</dock:LayoutPanel>
</dock:LayoutRoot>
</dock:DockingManager>
</Grid>
</Window>
Update:
My implementation of BionicCode's answer. My remaining issue is that if I close a pane, the menu item remains checked.
XAML
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Menu -->
<Menu Height="18" HorizontalAlignment="Stretch" Name="menu1" VerticalAlignment="Top" Grid.Row="0">
<MenuItem Header="File">
<MenuItem Header="_Foo1" Name="mnuPane1" IsCheckable="True" IsChecked="{Binding RelativeSource={RelativeSource AncestorType=local:MainWindow}, Path=IsAnchorable1Visible}"/>
<MenuItem Header="Foo2" Name="mnuPane2" IsCheckable="True" IsChecked="{Binding RelativeSource={RelativeSource AncestorType=local:MainWindow}, Path=IsAnchorable2Visible}"/>
</MenuItem>
</Menu>
<!-- AvalonDock -->
<dock:DockingManager x:Name="Dockman" DockPanel.Dock="Left" Grid.Row="1" >
<dock:LayoutRoot x:Name="_layoutRoot">
<dock:LayoutPanel Orientation="Horizontal">
<dock:LayoutAnchorablePaneGroup Orientation="Vertical">
<dock:LayoutAnchorablePane FloatingWidth="150" FloatingHeight="150" FloatingLeft="100" FloatingTop="300">
<dock:LayoutAnchorable ContentId="content1" x:Name="anchorable1" IsSelected="True" >
<GroupBox Header="Foo1"/>
</dock:LayoutAnchorable>
</dock:LayoutAnchorablePane>
<dock:LayoutAnchorablePane FloatingWidth="150" FloatingHeight="150" FloatingLeft="100" FloatingTop="300">
<dock:LayoutAnchorable ContentId="content2" x:Name="anchorable2" IsSelected="True" >
<GroupBox Header="Foo2"/>
</dock:LayoutAnchorable>
</dock:LayoutAnchorablePane>
</dock:LayoutAnchorablePaneGroup>
</dock:LayoutPanel>
</dock:LayoutRoot>
</dock:DockingManager>
</Grid>
Code behind
partial class MainWindow : Window
{
public static readonly DependencyProperty IsAnchorable1VisibleProperty = DependencyProperty.Register(
"IsAnchorable1Visible",
typeof(bool),
typeof(MainWindow),
new PropertyMetadata(default(bool), MainWindow.OnIsAnchorable1VisibleChanged));
public static readonly DependencyProperty IsAnchorable2VisibleProperty = DependencyProperty.Register(
"IsAnchorable2Visible",
typeof(bool),
typeof(MainWindow),
new PropertyMetadata(default(bool), MainWindow.OnIsAnchorable2VisibleChanged));
public bool IsAnchorable1Visible
{
get => (bool)GetValue(MainWindow.IsAnchorable1VisibleProperty);
set => SetValue(MainWindow.IsAnchorable1VisibleProperty, value);
}
public bool IsAnchorable2Visible
{
get => (bool)GetValue(MainWindow.IsAnchorable2VisibleProperty);
set => SetValue(MainWindow.IsAnchorable2VisibleProperty, value);
}
public MainWindow()
{
InitializeComponent();
this.IsAnchorable1Visible = true;
this.IsAnchorable2Visible = true;
}
private static void OnIsAnchorable1VisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as MainWindow).anchorable1.IsVisible = (bool)e.NewValue;
}
private static void OnIsAnchorable2VisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as MainWindow).anchorable2.IsVisible = (bool)e.NewValue;
}
}
The AvalonDock XAML layout elements are neither controls nor derived of UIElement. They serve as plain models (although they extend DependencyObject).
The properties of LayoutAnchorable are not implemented as DependencyProperty, but instead implement INotifyPropertyChanged (as said before, the layout elements serve as the control's view model). Hence they don't support data biding (as binding target).
Each of those XAML layout elements has a corresponding control which will be actually rendered with the layout element as DataContext. The names equal the layout element's name with the Control suffix attached. If you want to connect those controls or item containers e.g., LayoutAnchorableItem to your view model, you'd have to create a Style that targets this container. The next flaw is that the DataContext of this containers is not your data model that the control is intended to display, but the control's internal model. To get to your view model you would need to access e.g. LayoutAnchorableControl.LayoutItem.Model (because the LayoutAnchorableControl.DataContext is the LayoutAnchorable).
The authors obviously got lost while being too eager to implement the control itself using MVVM (as stated in their docs) and forget to target the MVVM client application. They broke the common WPF pattern. Looks good on the outside, but not so good on the inside.
To solve your problem, you have to introduce an intermediate dependency property on your view. A registered property changed callback would then delegate the visibility to toggle the visibility of the anchorable.
It's also important to note that the authors of AvalonDock didn't use the UIElement.Visibility to handle visibility. They introduced a custom visibility logic independent of the framework property.
As mentioned before, there is always the pure model driven approach, where you layout the initial view by providing a ILayoutUpdateStrategy implementation. You then define styles to wire up view and view models. Hardcoding the view using the XAML layout elements leads to certain inconvenience in more advanced scenarios.
LayoutAnchorable exposes a Show() and Close() method or the IsVisible property to handle visibility. You can also bind to a command when accessing LayoutAnchorableControl.LayoutItem (e.g. from within a ControlTemplate), which returns a LayoutAnchorableItem. This LayoutAnchorableItem exposes a HideCommand.
MainWindow.xaml
<Window>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Menu -->
<Menu Grid.Row="0">
<MenuItem Header="File">
<MenuItem Header="_Foo1"
IsCheckable="True"
IsChecked="{Binding RelativeSource={RelativeSource AncestorType=MainWindow}, Path=IsAnchorable1Visible}" />
</MenuItem>
</Menu>
<!-- AvalonDock -->
<dock:DockingManager Grid.Row="1" >
<dock:LayoutRoot>
<dock:LayoutPanel>
<dock:LayoutAnchorablePaneGroup>
<dock:LayoutAnchorablePane>
<dock:LayoutAnchorable x:Name="Anchorable1"
Hidden="Anchorable1_OnHidden">
<GroupBox Header="Foo1" />
</dock:LayoutAnchorable>
</dock:LayoutAnchorablePane>
</dock:LayoutAnchorablePaneGroup>
</dock:LayoutPanel>
</dock:LayoutRoot>
</dock:DockingManager>
</Grid>
</Window>
MainWindow.xaml.cs
partial class MainWindow : Window
{
public static readonly DependencyProperty IsAnchorable1VisibleProperty = DependencyProperty.Register(
"IsAnchorable1Visible",
typeof(bool),
typeof(MainWindow),
new PropertyMetadata(default(bool), MainWindow.OnIsAnchorable1VisibleChanged));
public bool IsAnchorable1Visible
{
get => (bool) GetValue(MainWindow.IsAnchorable1VisibleProperty);
set => SetValue(MainWindow.IsAnchorable1VisibleProperty, value);
}
public MainWindow()
{
InitializeComponent();
this.IsAnchorable1Visible = true;
}
private static void OnIsAnchorable1VisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as MainWindow).Anchorable1.IsVisible = (bool) e.NewValue;
}
private void Anchorable1_OnHidden(object sender, EventArgs e) => this.IsAnchorable1Visible = false;
}
There are two major issues with your bindings.
The IsVisible property is not a DependencyProperty, but just a CLR property, so you cannot bind it
A LayoutAnochorable is not part of the visual tree, so ElementName and RelativeSource bindings do not work, you will see the corresponding binding errors in your output window
I am not sure if there is a specific design choice or limitation to not make the IsVisible property a dependency property, but you can work around this by creating an attached property. This property can be bound and sets the CLR property IsVisible on the LayoutAnchorable when it changes.
public class LayoutAnchorableProperties
{
public static readonly DependencyProperty IsVisibleProperty = DependencyProperty.RegisterAttached(
"IsVisible", typeof(bool), typeof(LayoutAnchorableProperties), new PropertyMetadata(true, OnIsVisibleChanged));
public static bool GetIsVisible(DependencyObject dependencyObject)
{
return (bool)dependencyObject.GetValue(IsVisibleProperty);
}
public static void SetIsVisible(DependencyObject dependencyObject, bool value)
{
dependencyObject.SetValue(IsVisibleProperty, value);
}
private static void OnIsVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is LayoutAnchorable layoutAnchorable)
layoutAnchorable.IsVisible = (bool)e.NewValue;
}
}
You can bind this property in your XAML, but as being said, this will not work, because of the LayoutAnchorable not being in the visual tree. The same issue occurs for DataGrid columns. In this related post you find a workaround with a BindingProxy class that we will use. Please copy this class into your project.
Create an instance of the binding proxy in your DockingManager.Resources. It serves to access the menu item.
<dock:DockingManager x:Name="Dockman" DockPanel.Dock="Left" Grid.Row="1">
<dock:DockingManager.Resources>
<local:BindingProxy x:Key="mnuPane1Proxy" Data="{Binding ElementName=mnuPane1}"/>
</dock:DockingManager.Resources>
<!-- ...other XAML code. -->
</dock:DockingManager>
Remove your old IsVisible binding. Add a binding to the attached property using the mnuPane1Proxy.
<xcad:LayoutAnchorable ContentId="content1"
x:Name="anchorable1"
IsSelected="True"
local:LayoutAnchorableProperties.IsVisible="{Binding Data.IsChecked, Source={StaticResource mnuPane1Proxy}}">
Finally, set the default IsChecked state in your menu item to true, as that is the default state for IsVisible and the binding is not updated on initialization due to setting the default value in the attached properties, which is needed to prevent the InvalidOperationException that is thrown because the control is not completely initialized.
<MenuItem Header="_Foo1" Name="mnuPane1" IsCheckable="True" IsChecked="True">
Related
Background
I am making a custom control that has multiple ListBox's. I want to make this control MVVM compliant, so I am keeping any XAML and the code behind agnostic with respect to any ViewModel. One ListBox is simply going to be a list of TextBox's while the other is going to have a canvas as the host to display the data graphically. Both of these ListBox's are children of this custom control.
Pseudo example for the custom control template:
<CustomControl>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox1 Grid.Column="0"/>
<ListBox2 Grid.Column="1"/>
</CustomControl>
The code behind for this custrom control would have a dependency property that will serve as the ItemsSource, fairly standard stuff:
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(UserControl1), new PropertyMetadata(new PropertyChangedCallback(OnItemsSourcePropertyChanged)));
private static void OnItemsSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as UserControl1;
if (control != null)
control.OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue);
}
Where I am stuck
Because the two ListBox's are using the same data source but just display the data differently, I want the ItemsSource defined as one of the the parent view's dependency properties to be the ItemsSource for the two children. From the ViewModel side, this items source can be some sort of ObservableCollection<ChildViewModels>, or IEnumerable, or whatever it wants to be.
How can I point to properties from the ItemsSource's ViewModel to dependency properties of the child views?
I was hoping to get something similar to how it could be done outside of a custom view:
Example Parent ViewModel(omitting a lot, assume all functioning):
public class ParentViewModel
{
public ObservableCollection<ChildViewModel> ChildViewModels;
}
Example ViewModel (omitting INotifyPropertyChanged and associated logic):
public class ChildViewModel
{
public string Name {get; set;}
public string ID {get; set;}
public string Description {get; set;}
}
Example control (ommitting setting the DataContext, assume set properly):
<ListBox ItemsSource="{Binding ChildViewModels}">
<ListBox.ItemsTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text ="{Binding Description}"/>
</StackPanel>
</ListBox.ItemsTemplate>
</ListBox>
How can I do something similar where I can pass the properties from the ItemsSource to the child views on a custom control?
Many thanks
If I understand correctly what you need, then here is an example.
Add properties for element templates in both lists and style for Canvas.
using System.Collections;
using System.Windows;
using System.Windows.Controls;
namespace Core2022.SO.jgrmn
{
public class TwoListControl : Control
{
static TwoListControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TwoListControl), new FrameworkPropertyMetadata(typeof(TwoListControl)));
}
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
nameof(ItemsSource),
typeof(IEnumerable),
typeof(TwoListControl),
new PropertyMetadata((d, e) => ((TwoListControl)d).OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue)));
private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
//throw new NotImplementedException();
}
public DataTemplate TemplateForStack
{
get { return (DataTemplate)GetValue(TemplateForStackProperty); }
set { SetValue(TemplateForStackProperty, value); }
}
public static readonly DependencyProperty TemplateForStackProperty =
DependencyProperty.Register(
nameof(TemplateForStack),
typeof(DataTemplate),
typeof(TwoListControl),
new PropertyMetadata(null));
public DataTemplate TemplateForCanvas
{
get { return (DataTemplate)GetValue(TemplateForCanvasProperty); }
set { SetValue(TemplateForCanvasProperty, value); }
}
public static readonly DependencyProperty TemplateForCanvasProperty =
DependencyProperty.Register(
nameof(TemplateForCanvas),
typeof(DataTemplate),
typeof(TwoListControl),
new PropertyMetadata(null));
public Style StyleForCanvas
{
get { return (Style)GetValue(StyleForCanvasProperty); }
set { SetValue(StyleForCanvasProperty, value); }
}
public static readonly DependencyProperty StyleForCanvasProperty =
DependencyProperty.Register(
nameof(StyleForCanvas),
typeof(Style),
typeof(TwoListControl),
new PropertyMetadata(null));
}
}
In the theme (Themes/Generic.xaml), set bindings to these properties:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:jgrmn="clr-namespace:Core2022.SO.jgrmn">
<Style TargetType="{x:Type jgrmn:TwoListControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type jgrmn:TwoListControl}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0"
ItemsSource="{TemplateBinding ItemsSource}"
ItemTemplate="{TemplateBinding TemplateForStack}"/>
<ListBox Grid.Column="1"
ItemsSource="{TemplateBinding ItemsSource}"
ItemTemplate="{TemplateBinding TemplateForCanvas}"
ItemContainerStyle="{TemplateBinding StyleForCanvas}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ListBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Window with an example of use:
<Window x:Class="Core2022.SO.jgrmn.TwoListWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Core2022.SO.jgrmn"
mc:Ignorable="d"
Title="TwoListWindow" Height="250" Width="400">
<FrameworkElement.DataContext>
<CompositeCollection>
<Point>15 50</Point>
<Point>50 150</Point>
<Point>150 50</Point>
<Point>150 150</Point>
</CompositeCollection>
</FrameworkElement.DataContext>
<Grid>
<local:TwoListControl ItemsSource="{Binding}">
<local:TwoListControl.TemplateForStack>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}Point ({0} {1})">
<Binding Path="X"/>
<Binding Path="Y"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</local:TwoListControl.TemplateForStack>
<local:TwoListControl.TemplateForCanvas>
<DataTemplate>
<Ellipse Width="10" Height="10" Fill="Red"/>
</DataTemplate>
</local:TwoListControl.TemplateForCanvas>
<local:TwoListControl.StyleForCanvas>
<Style TargetType="ListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</local:TwoListControl.StyleForCanvas>
</local:TwoListControl>
</Grid>
</Window>
You must spend all participating controls a ItemsSource property. The idea is to delegate the source collection from the parent to the child controls and finally to the ListBox. The ItemsSource properties should be a dependency property of type IList and not IEnumerable. This way you force the binding source to be of type IList which improves the binding performance.
To allow customization of the actual displayed items, you must either
a) spend every control a ItemTemplate property of type DataTemplate and delegate it to the inner most ListBox.ItemTemplate (similar to the ItemsSource property) or
b) define the template as a resource (implicit template, which is a key less DataTemplate).
The example implements a):
<Window>
<Window.DataContext>
<ParentViewModel />
</Window.DataCOntext>
<CustomControl ItemsSource="{Binding ChildViewModels}">
<CustomControl.ItemsTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text ="{Binding Description}"/>
</StackPanel>
</CustomControl.ItemsTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox1 Grid.Column="0"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemsSource}"
ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
<ListBox2 Grid.Column="1"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemsSource}"
ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
</CustomControl>
</Window>
Inside the child controls (ListBox1 and ListBox2):
<UserControl>
<ListBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemsSource}"
ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
</UserControl>
I have created this UserControl
public partial class Dropdown : UserControl
{
public string Title { get; set; } = string.Empty;
private void loadSuccess(object sender, RoutedEventArgs e)
{
TitleBlock.Text = Title;
}
public Dropdown()
{
Loaded += loadSuccess;
InitializeComponent();
}
}
<UserControl x:Class="Lamprey.UserControls.Dropdown"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Lamprey.UserControls"
mc:Ignorable="d"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="17"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Text=">" Grid.Row="0" Grid.Column="0" Foreground="#FFC7C7C7"></TextBlock>
<TextBlock x:Name="TitleBlock" Grid.Row="0" Grid.Column="1"></TextBlock>
<ListBox x:Name="Items" Grid.Column="1" Grid.Row="1" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="White">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Focusable" Value="False"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</UserControl>
It is meant to be used like this
<usercontrols:Dropdown Title="History">
<ListBoxItem>
<TextBlock>Example</TextBlock>
</ListBoxItem>
</usercontrols:Dropdown>
I am trying to figure out how I can get a list of children of the <Dropdown> so I can move them into the Items <ListBox>.
The end goal is for any children addded to <Dropdown> to be automatically added to the <ListBox> named Items. That way, the entire listbox can be hidden when the dropdown is closed, and shown when the dropdown is opened.
The key is to define a collection dependency property and bind it to the ListBox. This property must be declared as the control's content property in order to be able to add items to it implicitly in XAML.
Define a custom non-generic collection or use an existing one
DropDownItemCollection.cs
public class DropDownItemCollection : List<object>
{ }
Define a dependency property of the non-generic collection type
Declare this property as the content property using the ContentProperty attribute to decorate the property owner
Optionally, define a DataTemplate type property e.g., ItemTemplate to allow templating the items using an explicit DataTemplate
DropDown.xaml.cs
[ContentProperty(nameof(DropDown.DropDownItems))]
public partial class DropDown : UserControl
{
public DropDownItemCollection DropDownItems
{
get => (DropDownItemCollection)GetValue(DropDownItemsProperty);
set => SetValue(DropDownItemsProperty, value);
}
public static readonly DependencyProperty DropDownItemsProperty =
DependencyProperty.Register(
"DropDownItems",
typeof(DropDownItemCollection),
typeof(DropDown),
new PropertyMetadata(default));
public DataTemplate ItemTemplate
{
get => (DataTemplate)GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register(
"ItemTemplate",
typeof(DataTemplate),
typeof(DropDown),
new PropertyMetadata(default));
public DropDown()
{
InitializeComponent();
this.DropDownItems = new DropDownItemCollection();
}
}
Bind the internal ListBox to the new DropDownItems property
DropDown.xaml
<UserControl>
<ListBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=DropDownItems}"
ItemTemplate="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ItemTemplate}" />
</UserControl>
Example
<!--
Since the collection is of type object you can add anything to the collection.
The ItemTemplate property allows to explicitly template the item layout.
-->
<Window>
<Window.Resources>
<DataTemplate x:Key="DroptDownItemTemplate">
<Grid>
<Border Background="OrangeRed" />
<TextBlock Text="{Binding}" />
</Grid>
</DataTemplate>
</Window.Resources>
<DropDown ItemTemplate={StaticResource DroptDownItemTemplate}">
<sys:String>Item 1</sys:String>
<sys:String>Item 2</sys:String>
</DropDown>
</Window>
Remarks
You can skip all this when extending ListBox instead of UserControl. Doing so, your control will support item creation in XAML and templating out of the box.
Hiding and showing the internal ListBox will lead to changes in the layout and therfore result in ugly content resizing. You should consider to host the ListBox (or in case of extending ListBox the ItemsPresenter) inside a Popup to create the flyout that overlays instead of resizing.
I have a user control that have a multi buttons and In the application i use this user control on multi windows ,but i want to Collapsed (shown/hidden) some buttons if the user select in the application a window 1 and show same button if the user select in the application a window 2
UserControl
<Grid x:Name="girdBtuWidow" >
<StackPanel Orientation="Horizontal">
<Button Content="add" x:Name="add" Visibility="{Binding window1_Loaded}" x:FieldModifier="public" Height="50" Width="100" Margin="0" Click="add_click" />
<Button Content="show history" x:Name="Personal" Height="50" Width="100" Margin="0" />
<Button Content="Show Customer" x:Name="Customer" Height="50" Width="100" Margin="0" />
</StackPanel>
</Grid>
how to set the property (Visibility) of button in the User Control from the application window ?
You don't need to use a Window_Loaded Event here.
You need to expose a Visibility property for each of your buttons in your UserControls.
In your UserControl add a binding to each button for the Visibility property:
Visibility="{Binding AddButtonVisibility}"
Visibility="{Binding ShowHistoryButtonVisibility}"
Visibility="{Binding ShowCustomerButtonVisibility}"
Make sure you add a DataContext to your UserControl, I generally use Self:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
In your UserControl Code Behind add Dependency Properties for each of the Bindings above:
public Visibility AddButtonVisibility
{
get { return (Visibility)GetValue(AddButtonVisibilityProperty); }
set { SetValue(AddButtonVisibilityProperty, value); }
}
// Using a DependencyProperty as the backing store for AddButtonVisibility. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AddButtonVisibilityProperty =
DependencyProperty.Register("AddButtonVisibility", typeof(Visibility), typeof(UserControl1), new PropertyMetadata(Visibility.Visible));
public Visibility ShowHistoryButtonVisibility
{
get { return (Visibility)GetValue(ShowHistoryButtonVisibilityProperty); }
set { SetValue(ShowHistoryButtonVisibilityProperty, value); }
}
// Using a DependencyProperty as the backing store for ShowHistoryButtonVisibility. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ShowHistoryButtonVisibilityProperty =
DependencyProperty.Register("ShowHistoryButtonVisibility", typeof(Visibility), typeof(UserControl1), new PropertyMetadata(Visibility.Visible));
public Visibility ShowCustomerButtonVisibility
{
get { return (Visibility)GetValue(ShowCustomerButtonVisibilityProperty); }
set { SetValue(ShowCustomerButtonVisibilityProperty, value); }
}
// Using a DependencyProperty as the backing store for ShowCustomerButtonVisibility. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ShowCustomerButtonVisibilityProperty =
DependencyProperty.Register("ShowCustomerButtonVisibility", typeof(Visibility), typeof(UserControl1), new PropertyMetadata(Visibility.Visible));
In Visual Studio, There is a code snippet shortcut for Dependency Properties - type propdp and hit tab twice.
Now, to use the properties you have just created put the Usercontrol onto the relevant window:
<local:UserControl1 AddButtonVisibility="Collapsed" />
local is the project namespaces' alias - defined at the top of your Window. You can just drag and drop the UserControl onto your window, and it will do this for you. (You may need to rebuild in order to see your UserControls in your Toolbox.
You should now see your control with the Add Button collapsed.
For completeness sake, here is the XAML side of things:
UserControl Xaml:
<UserControl x:Class="WpfApplication2.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid x:Name="girdBtuWidow" >
<StackPanel Orientation="Horizontal">
<Button Content="Add" x:Name="add" Height="50" Width="100" Margin="0" Click="Add_Click" Visibility="{Binding AddButtonVisibility}"/>
<Button Content="Show History" x:Name="Personal" Height="50" Width="100" Margin="0" Click="ShowHistory_Click" Visibility="{Binding ShowHistoryButtonVisibility}" />
<Button Content="Show Customer" x:Name="Customer" Height="50" Width="100" Margin="0" Click="ShowCustomer_Click" Visibility="{Binding ShowCustomerButtonVisibility}"/>
</StackPanel>
</Grid>
Window1.Xaml:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2" x:Class="WpfApplication2.Window1"
Title="Window1" Height="300" Width="308">
<Grid>
<local:UserControl1 HorizontalAlignment="Left" VerticalAlignment="Top" AddButtonVisibility="Collapsed" />
</Grid>
public static readonly DependencyProperty onBackVisibilityProperty =
DependencyProperty.Register("onBackVisibility", typeof(Visibility), typeof(MyToolBar), new PropertyMetadata(Visibility.Visible));
public Visibility onBackVisibility
{
get { return (Visibility)GetValue(onBackVisibilityProperty); }
set { SetValue(onBackVisibilityProperty, value); }
}
public static readonly DependencyProperty onBackVisibilityProperty =
DependencyProperty.Register("onBackVisibility", typeof(Visibility), typeof(MyToolBar), new PropertyMetadata(Visibility.Visible));
public Visibility onBackVisibility
{
get { return (Visibility)GetValue(onBackVisibilityProperty); }
set { SetValue(onBackVisibilityProperty, value); }
}
//After this goto xaml
Button Name="tbrBack"
ToolTip="{DynamicResource Back}"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Click="tbrBack_Click"
Visibility="{Binding onBackVisibility ,ElementName = usercontrol}
I'm realtivley new to C# and WPF and have gotten the task to program an Alarm. Now I have the problem, that I have to save the set times to a textfile when closing the mainwindow.
The times are stored in an ObservableCollection of the type Reminder, a class i wrote myself and stores the time and name of the alarm as string.
public override string ToString()
{
return ReminderName + " " + ReminderTime.ToString("HH:mm");
}
My saving function looks like this:
...
public RelayCommand<object> SaveAllTimes { get; set; }
...
public MainWindowModel()
{
...
SaveAllTimes = new RelayCommand<object>(SaveReminders, CanSaveReminders);
...
}
private void SaveReminders(object sender)
{
StreamWriter writer = new StreamWriter("time.txt");
foreach (Reminder time in Reminders)
{
writer.WriteLine(time.ToString());
}
}
Now how can I bind the view to this function, that it's executed when the user closes it?
My view looks like this:
<Window x:Class="Wecker1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Wecker1"
Height="350"
Width="310" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="3*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<ListBox Name="Liste" ItemsSource="{Binding Reminders}" Margin="10" Grid.Row="0">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" IsChecked="{Binding Active}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Button Command="{Binding SaveTime}" Content="Add Reminder" Margin="10" Grid.Column="1"/>
<Button Margin="10" Content="Stop" Command="{Binding DeleteTime}"
CommandParameter="{Binding ElementName=Liste,Path=SelectedItem}" />
</Grid>
</Grid>
</Window>
here is my approach using pure MVVM, not dependent to any other provider
xaml
<Window x:Class="CSharpWPF.ViewModel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:CSharpWPF"
l:MyEventHandler.ClosingCommand="{Binding SaveAllTimes}">
</Window>
MyEventHandler class
namespace CSharpWPF
{
public class MyEventHandler
{
public static ICommand GetClosingCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(ClosingCommandProperty);
}
public static void SetClosingCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(ClosingCommandProperty, value);
}
// Using a DependencyProperty as the backing store for ClosingCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ClosingCommandProperty =
DependencyProperty.RegisterAttached("ClosingCommand", typeof(ICommand), typeof(MyEventHandler), new PropertyMetadata(OnClosingCommandChanged));
private static void OnClosingCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Window window = d as Window;
window.Closing += (s, ee) => GetClosingCommand(d).Execute(ee);
}
}
}
so the whole idea is to route the events to binded commands via Attached Properties, you may create more handlers as you need
Somewhere in your code, you are creating your View and ViewModel. From my point of view (and this part surely is opinion-based) you should implement your On-Exit code there, because your saving of data is at the end of the process, not the end of the view. When your MainWindow was run, you can call a viewmodel method to save all it's relevant data.
If you do want to have it upon closing the view instead of ending the program, you can go two paths: the dark side, by writing a code-behind OnClose handler for the window. Don't tell anyone I said so. That's not WPF style. Or the correct path by implementing a Close-Behavior for your window. That's too broad for a single post, you should look up WPF, View and Behavior, you will find lots of tutorials for different behaviors.
You can use interactivity to bind Closing event to your Command like below
<Window xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<i:InvokeCommandAction Command="{Binding SaveAllTimes }"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Window>
I have created a very simple user control, an ImageButton
<UserControl x:Class="SampleApp.Controls.ImageButton"
Name="ImageButtonControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Button>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="6*" />
<RowDefinition Height="2*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Image Grid.Row="1" Source="{Binding ElementName=ImageButtonControl, Path=Image}" VerticalAlignment="Stretch" HorizontalAlignment="Center" />
<TextBlock Grid.Row="2" Text="{Binding ElementName=ImageButtonControl, Path=Text}" VerticalAlignment="Stretch" HorizontalAlignment="Center" />
</Grid>
</Button>
</UserControl>
With code behind:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SampleApp.Controls
{
/// <summary>
/// Interaction logic for ImageButton.xaml
/// </summary>
public partial class ImageButton : UserControl
{
public ImageButton()
{
InitializeComponent();
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(ImageButton), new UIPropertyMetadata(""));
public ImageSource Image
{
get { return (ImageSource)GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
public static readonly DependencyProperty ImageProperty =
DependencyProperty.Register("Image", typeof(ImageSource), typeof(ImageButton), new UIPropertyMetadata(null));
}
}
Now I want to use that in my little sample application like
xmlns:controls="clr-namespace:SampleApp.Controls"
<controls:ImageButton Grid.Row="1"
Grid.Column="1"
Margin="2"
Image="/Images/link.png"
Text="DoSomething">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="DoSomething" />
</i:EventTrigger>
</i:Interaction.Triggers>
</controls:ImageButton>
If I give it a x:Name, like
<controls:ImageButton x:Name="DoSomething"
e.g. DoSomething the method DoSomething with that name is directly called when the view is shown, i.e. when I active the viewmodel that contains that button, just like I click the Button (if it was a normal button and not a usercontrol, it would work that way), but the button-click handler is never called on clicking.
Now I tried to add an ActionMessage as seen above, but it does not work either...
What is wrong here?
That's because there is no convention configured for your user control type. You could either add a convention via the ConventionManager (see http://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Conventions), or you could derive your type from Button instead.
You could also not use a custom user control and instead just add the image to the Content property of the Button in your view.