How to attach trigger on ControlTemplate items? - c#

We are trying to bind Trigger on Stackpanel loaded while using RadMessageBox control. Example :
<!-- CustomMessageBox Template -->
<ControlTemplate x:Key="MessageBoxTemplate" TargetType="messageBox:RadMessageBoxControl">
<Border Padding="12" Background="{StaticResource PhoneChromeBrush}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ContentControl x:Name="PART_Title" Grid.Row="0"
HorizontalContentAlignment="Left"
FontSize="{StaticResource PhoneFontSizeLarge}"
FontFamily="{StaticResource PhoneFontFamilySemiBold}"
Margin="{StaticResource PhoneMargin}"/>
<ContentControl HorizontalContentAlignment="Left" Grid.Row="1"
VerticalContentAlignment="Top" Margin="{StaticResource PhoneMargin}"
x:Name="PART_Message"/>
<CheckBox x:Name="PART_CheckBox" Grid.Row="2"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"/>
<ContentControl x:Name="PART_ButtonsContainer" Grid.Row="3"
HorizontalContentAlignment="Stretch" Margin="12,0" Width="440">
<ContentControl.ContentTemplate>
<DataTemplate>
<StackPanel x:Name="PART_ButtonsPanel"
Orientation="Vertical" HorizontalAlignment="Stretch">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding DataContext.CustomMessageBoxStackPanelLoadedCommand}" CommandParameter="{Binding PART_ButtonsPanel}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</StackPanel>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</Border>
</ControlTemplate>
The ControlTemplate is inside Page.Resources.
We are unable to Trigger stackpanel loaded event. Code behind file :
private bool _CustomMessageBoxStackPanelLoadedCommandCanExecute = true;
bool CustomMessageBoxStackPanelLoadedCommandCanExecute
{
get
{
return _CustomMessageBoxStackPanelLoadedCommandCanExecute;
}
set
{
if (_CustomMessageBoxStackPanelLoadedCommandCanExecute == value)
{
return;
}
_CustomMessageBoxStackPanelLoadedCommandCanExecute = value;
RaisePropertyChanged("CustomMessageBoxStackPanelLoadedCommandCanExecute");
if (_CustomMessageBoxStackPanelLoadedCommand != null)
_CustomMessageBoxStackPanelLoadedCommand.RaiseCanExecuteChanged();
}
}
private RelayCommand<string> _CustomMessageBoxStackPanelLoadedCommand;
public ICommand CustomMessageBoxStackPanelLoadedCommand
{
get
{
if (_CustomMessageBoxStackPanelLoadedCommand == null)
{
_CustomMessageBoxStackPanelLoadedCommand = new RelayCommand<string>(CustomMessageBoxStackPanelLoaded, (data) => CustomMessageBoxStackPanelLoadedCommandCanExecute);
}
return _CustomMessageBoxStackPanelLoadedCommand;
}
}
private void CustomMessageBoxStackPanelLoaded(object obj)
{
System.Diagnostics.Debug.WriteLine("Hello");
}

The problem is with your CommandBinding. your binding is:
Command="{Binding DataContext.CustomMessageBoxStackPanelLoadedCommand}"
that means that in the DataContext class of your control there is any object name DataContext which has CustomMessageBoxStackPanelLoadedCommand as a property in it. But that is not the case. Such type of syntax is used in RelativeSource Binding.
But here change your binding as:
Command="{Binding CustomMessageBoxStackPanelLoadedCommand}"
So binding engine will now find this command directly inside your DataContext. (I hope there is not any problem with giving datacontext to your views.)
also not sure why you have written
CommandParameter="{Binding PART_ButtonsPanel}"
like this. what are you trying to do there?

Related

How to add multiple elements to the wrappanel?

I am trying to insert several StackPanel inside the WrapPanel as you can see the XAML below:
Only the TextBlock inside the StackPanel will be modified so as not to repeat the Title and Text.
<Window x:Class="ambient_test.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:local="clr-namespace:ambient_test"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ScrollViewer CanContentScroll="True">
<WrapPanel x:Name="wrappanel">
<StackPanel x:Name="panel1" Width="120" Margin="5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="120"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Background="#FF38C59F"></StackPanel>
<TextBlock Grid.Row="2" Text="Title 1" Foreground="LightGray" VerticalAlignment="Top" TextAlignment="Center"/>
<TextBlock Grid.Row="1" Text="Text 1" Foreground="#FF747474" TextAlignment="Center" Margin="0 15 0 0"/>
</Grid>
</StackPanel>
<StackPanel x:Name="panel2" Width="120" Margin="5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="120"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Background="#FF38C59F"></StackPanel>
<TextBlock Grid.Row="2" Text="Title 2" Foreground="LightGray" VerticalAlignment="Top" TextAlignment="Center"/>
<TextBlock Grid.Row="1" Text="Text 2" Foreground="#FF747474" TextAlignment="Center" Margin="0 15 0 0"/>
</Grid>
</StackPanel>
</WrapPanel>
</ScrollViewer>
</Grid>
</Window>
I created a class called Info to change the Title and the Text. In the method constructor I have a for loop that will add 10 contents to the List.
public MainWindow()
{
InitializeComponent();
List<Info> infos = new List<Info>();
for (int i = 0; i < 10; i++)
{
infos.Add(new Info()
{
Title = $"title {i}",
Text = $"text {i}"
});
}
}
public class Info
{
public string Title { get; set; }
public string Text { get; set; }
}
For example, my List has 100 records, so I need to add 100 StackPanel inside my WrapPanel following the same Title and Text as the List.
Is there any way to do this? For example using Binding?
You would use an ItemsControl that uses a WrapPanel as its ItemsPanel, and defines the layout of the items by an appropriate DataTemplate:
<ItemsControl x:Name="itemsControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="120"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Title}" .../>
<TextBlock Grid.Row="1" Text="{Binding Text}" .../>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
and assign the List<Info> to its ItemsSource property:
itemsControl.ItemsSource = infos;

WPF ListView - detect ListVewItem when selected item is not clicked

I'm trying to get an item in ListView in WPF when user doesn't select an item by clicking on it but the user taps on it.
Is there a way to achieve this in WPF ListView.
ListView dataSource is filled by a dynamic collection in code-behind.
I'm getting a null object when user taps on it.
Item selectedItem = (Item)lv_CartItems.SelectedItem;
this is how my data-template looks like for listview.
<ListView.ItemTemplate>
<DataTemplate>
<Viewbox>
<Grid Width="230" Height="110" >
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width=".1*" />
<ColumnDefinition />
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border BorderBrush="LightGray" BorderThickness="1"
Grid.Row="0" Grid.Column="0"
Grid.ColumnSpan="6" Grid.RowSpan="3" >
</Border>
<Viewbox Grid.Row="0" >
<Image Name="img_ItemImage"
Source="{Binding Image, Mode=TwoWay }"
Width="20" Height=" 25" />
</Viewbox>
<Viewbox Grid.Column="2" Grid.ColumnSpan="3" VerticalAlignment="Top" >
<TextBlock Name="lbl_ItemName" TextWrapping="Wrap" Width="180" Foreground="Gray"
Text="{Binding Name , Mode=TwoWay }" Tag="{Binding SKU_No,Mode=TwoWay}" >
</TextBlock>
</Viewbox>
<Viewbox Grid.Row="1" Margin="10,0" VerticalAlignment="Top" >
<TextBlock Foreground="Gray" >Qty:</TextBlock>
</Viewbox>
<Viewbox Grid.Row="2" Margin="0,0" VerticalAlignment="Top" >
<StackPanel Orientation="Horizontal" >
<Button Name="btn_Minus" FontWeight="ExtraBold" Padding="0" Width="12"
Resources="{StaticResource cartitembutton}" Click="btn_Minus_Click" >
<Image Source="/Resources\Icons\minus.png" ></Image>
</Button>
<Border BorderThickness="1" Margin="2,0" Width="13" CornerRadius="2" BorderBrush="LightGray" >
<TextBlock Name="lbl_Quantity" FontWeight="Bold" Foreground="Gray"
HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Quantity , Mode=TwoWay }">
</TextBlock>
</Border>
<Button Name="btn_Increment" FontWeight="ExtraBold" Width="12"
Resources="{StaticResource cartitembutton}"
Padding="0"
Click="btn_Increment_Click">
<Image Source="/Resources\Icons\union_10.png" ></Image>
</Button>
</StackPanel>
</Viewbox>
<Viewbox Grid.Row="1" Grid.Column="2" Margin="5,0"
HorizontalAlignment="Left" Grid.ColumnSpan="3" >
<TextBlock Name="lbl_Price" FontWeight="DemiBold"
Text="{Binding Price , Mode=TwoWay}" ></TextBlock>
</Viewbox>
<Viewbox Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="3"
VerticalAlignment="Top" Margin="0,0" >
<TextBlock Name="lbl_Appearence"
Text="{Binding Appearance , Mode=TwoWay }"
TextWrapping="Wrap" Foreground="Gray" Width="210" >
</TextBlock>
</Viewbox>
<Viewbox Grid.Column="5" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="2,2"
>
<Button Name="btn_DeleteItem"
Click="btn_DeleteItem_Click"
Resources="{StaticResource cartitembutton}" >
<Image Source="/Resources/Icons/delete.png" ></Image>
</Button>
</Viewbox>
</Grid>
</Viewbox>
</DataTemplate>
</ListView.ItemTemplate>
You should use data binding to retrieve the selected item. The source property will be automatically updated when the ListView.SelectedItem changes.
Touch is handled the same way as click is handled. The framework converts touch events to mouse events.
I noticed that you wrapped every element into a ViewBox. That is not necessary and will only degrade performance. When the elements are hosted inside a Grid they will resize automatically by default. Exceptions are elements that need an initial size like Shape or Image. But since the item containers won't resize themselves, the content of the containers won't resize too.
To force all elements to occupy max horizontal space you can set a Style that targets the item container:
<ListView.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
"Items",
typeof(ObservableCollection<Item>),
typeof(MainWindow),
new PropertyMetadata(default(ObservableCollection<Item>)));
public ObservableCollection<Item> Items
{
get => (ObservableCollection<Item>) GetValue(MainWindow.ItemsProperty);
set => SetValue(MainWindow.ItemsProperty, value);
}
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register(
"SelectedItem",
typeof(Item),
typeof(MainWindow),
new PropertyMetadata(default(Item), MainWindow.OnSelectedItemChanged));
public Item SelectedItem
{
get => (Item) GetValue(MainWindow.SelectedItemProperty);
set => SetValue(MainWindow.SelectedItemProperty, value);
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.Items = new ObservableCollection<Item>();
// Initialize data source
CreateItems();
}
// Property changed callback of the SelectedItem property
private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Reference to instance members in a class scope
var _this = d as MainWindow;
if (e.NewValue is ItemCollection selectedItem)
{
// Handle currently selected item
}
}
}
MainWindow.xaml
<Window>
<ListView ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}" />
</Window>

UWPToolkit - What is a ItemTemplate and how do I make one for my AdaptiveGrid?

Im currently trying to use the AdaptiveGrid in the toolkit to display Some images that then can be clicked on to lead to pages, I think this ItemTemplate is what provides the images in the example. Is this the same as any DataTemplate? I cant find any information online about these or docs.
<Controls:AdaptiveGridView Name="AdaptiveGridViewControl"
OneRowModeEnabled="False"
ItemHeight="200"
DesiredWidth="300"
SelectionMode="Single"
IsItemClickEnabled="True"
ItemTemplate="{StaticResource PhotosTemplate}"/>
I've tried the following, I get no errors but nothing shows up when I run it locally except the command bar:
Mainpage.xaml:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MobileAppProject"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="using:ViewModels"
xmlns:Controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
x:Class="MobileAppProject.MainPage"
mc:Ignorable="d">
<Page.Resources>
<DataTemplate x:Key="AdaptTemplate">
<Grid
Background="White"
BorderBrush="Black"
BorderThickness="1">
<Image
Source="{Binding Image}"
Stretch="UniformToFill"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ScrollViewer VerticalScrollMode="Auto" VerticalScrollBarVisibility="Auto">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Margin="12,10,12,12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Margin="0,0,0,10"/>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Controls:AdaptiveGridView Name="AdaptiveGridViewControl"
OneRowModeEnabled="False"
ItemHeight="200"
DesiredWidth="300"
SelectionMode="Single"
IsItemClickEnabled="True"
ItemTemplate="{StaticResource AdaptTemplate}"/>
<StackPanel VerticalAlignment="Bottom" Margin="0,24,0,0" Grid.Row="1" Background="{ThemeResource SystemControlBackgroundAccentBrush}">
<CommandBar x:Name="cmdbar"
IsOpen="{Binding IsChecked, ElementName=isopentoggle, Mode=TwoWay}"
IsSticky="{Binding IsChecked, ElementName=isstickytoggle, Mode=TwoWay}"
ClosedDisplayMode="{Binding SelectedItem.Content, ElementName=combobox}">
<CommandBar.SecondaryCommands>
<AppBarButton Label="Menu Item 1"/>
<AppBarButton Label="Menu Item 2"/>
<AppBarButton Label="Menu Item 3"/>
<AppBarButton Label="Menu Item 4"/>
</CommandBar.SecondaryCommands>
<AppBarButton Icon="Accept" Label="Accept"/>
<AppBarToggleButton Icon="Contact" Label="Contact"/>
</CommandBar>
<Image HorizontalAlignment="Left" Source="Assets/storeLogo-sdk.png" Stretch="None"/>
</StackPanel>
</Grid>
<!-- Status Block for providing messages to the user. Use the
NotifyUser() method to populate the message -->
<TextBlock x:Name="StatusBlock" Grid.Row="2" Margin="12, 10, 12, 10" Visibility="Collapsed"/>
</Grid>
</ScrollViewer>
</Grid>
</Page>
Mainpage.xaml.cs:
private ObservableCollection<AdaptItem> picItems_;
private ObservableCollection<AdaptItem> PicItems
{
get
{
return picItems_;
}
set
{
picItems_ = value;
}
}
public MainPage()
{
this.InitializeComponent();
picItems_ = AdaptItem.AdaptList();
this.DataContext = PicItems;
}
AdaptTemplate.cs for filling the AdaptGrid:
public class AdaptItem
{
public String Image
{
get;
set;
}
}
public static ObservableCollection<AdaptItem> AdaptList()
{
ObservableCollection<AdaptItem> pics = new ObservableCollection<AdaptItem>()
{
new AdaptItem
{
Image = "Assets/01.jpg"
},
new AdaptItem
{
Image = "Assets/02.jpg"
},
new AdaptItem
{
Image = "Assets/03.jpg"
},
new AdaptItem
{
Image = "Assets/04.jpg"
},
new AdaptItem
{
Image = "Assets/05.jpg"
}
};
return pics;
}
You can check how the template is used in the UWP Toolkit sample app or in the sample code on GitHub:
<Controls:AdaptiveGridView Name="AdaptiveGridViewControl"
OneRowModeEnabled="False"
ItemHeight="200"
DesiredWidth="300"
SelectionMode="Single"
IsItemClickEnabled="True"
ItemTemplate="{StaticResource PhotosTemplate}">
<Controls:AdaptiveGridView.Resources>
<DataTemplate x:Key="PhotosTemplate">
<Grid
Background="White"
BorderBrush="Black"
BorderThickness="1">
<Image
Source="{Binding Thumbnail}"
Stretch="UniformToFill"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
</Controls:AdaptiveGridView.Resources>
</Controls:AdaptiveGridView>
In short - yes, the ItemTemplate works the same way as any other DataTemplate.

BindingExpression Path Error - How to structure code in MVVM model?

I have a Button that when clicked should adjust the Widths of two Grids.
<DataTemplate x:Key="ItemTemplate">
<DockPanel Width="Auto">
<Button Click="SelectMovie_Click" DockPanel.Dock="Top">
<Button.Template>
<ControlTemplate >
<Image Source="{Binding image}"/>
</ControlTemplate>
</Button.Template>
<i:Interaction.Behaviors>
<local:UniqueNameBehavior ID="{Binding id}"/>
</i:Interaction.Behaviors>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding ShowRightGridCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<TextBlock Text="{Binding title}" HorizontalAlignment="Center" DockPanel.Dock="Bottom"/>
</DockPanel>
</DataTemplate>
This Button is displayed within a Grid as such:
<Grid Grid.Row="2" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding LeftGridWidth}" />
<ColumnDefinition Width="{Binding RightGridWidth}" />
</Grid.ColumnDefinitions>
// BUTTONS DISPLAYED HERE
<Grid x:Name="LeftGrid" Grid.Row="2" Grid.Column="0" >
<Border BorderThickness="1" BorderBrush="Red">
<ItemsControl ItemTemplate="{StaticResource ItemTemplate}" ItemsSource="{Binding _movies}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Border>
</Grid>
<Grid x:Name="RightGrid" HorizontalAlignment="Left" Grid.Row="2" Grid.Column="1" >
<DockPanel>
<StackPanel VerticalAlignment="Top" Height="200">
<TextBlock Width="200" Height="50" DockPanel.Dock="Top" HorizontalAlignment="Left">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path = "SelectedMovie.title"/>
<Binding Path = "SelectedMovie.year"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
<StackPanel VerticalAlignment="Top" Height="200">
<Grid HorizontalAlignment="Center">
<Image Source="star_icon.png" Width="100" Height="100" VerticalAlignment="Top"/>
<TextBlock Text="{Binding SelectedMovie.rating}" Style="{StaticResource AnnotationStyle}" Width="150"/>
</Grid>
</StackPanel>
</DockPanel>
</Grid>
</Grid>
ViewModel
public List<MediaDetail> _movies { get; set; }
public string selectedMovieID { get; set; }
private GridLength _leftGridWidth;
private GridLength _rightGridWidth;
private readonly GridLength _defaultRightGridWidth = new GridLength(0, GridUnitType.Pixel);
public MoviePanelViewModel()
{
ShowRightGridCommand = new DelegateCommand(SwitchRightGridWidth);
LeftGridWidth = new GridLength(7, GridUnitType.Star);
RightGridWidth = _defaultRightGridWidth;
}
private void SwitchRightGridWidth()
{
RightGridWidth = RightGridWidth == _defaultRightGridWidth ? new GridLength(3, GridUnitType.Star) : _defaultRightGridWidth;
}
public GridLength LeftGridWidth
{
get { return _leftGridWidth; }
set { _leftGridWidth = value; OnPropertyChanged("LeftGridWidth"); }
}
public GridLength RightGridWidth
{
get { return _rightGridWidth; }
set { _rightGridWidth = value; OnPropertyChanged("RightGridWidth"); }
}
public ICommand ShowRightGridCommand { get; set; }
The problem is that when I run my code, I get the error:
BindingExpression path error: 'ShowRightGridCommand' property not found on 'object' ''MediaDetail'
I am not sure how to structure my code such that the Width properties for the Grids can remain in the ViewModel but the Trigger for my Button also works. The DataContext for my View is the ViewModel.
Is there a good way to achieve this?
Thank you for your help.
The problem is your binding inside a DataTemplate: WPF tries to find the property path ShowRightGridCommand on your MediaDetail class, because the ItemsControl sets the data context for each item container it generates to the corresponding item.
What you actually want to do is binding to the view model. You can do this like so:
{Binding ElementName=LeftGrid, Path=DataContext.ShowRightGridCommand}
LeftGrid is an element outside of the ItemsControl. Its DataContext is your MoviePanelViewModel instance, and there is your ShowRightGridCommand property defined.
A little cleaner would be to put the name root on your view's root control (usually a UserControl or Window), and then use ElementName=root,Path=DataContext.... as your binding - at least that's what I usually do.
An alternative would be to use RelativeSource={RelativeSource UserControl}, but I find ElementName=root to be simpler.

Custom Layout Control and the Logical Tree

I've been working on a control for a while that can be used to repeat a layout in our application. It's working great but the problem is that the controls inside the layout control aren't participating in the logical tree.
It's a custom control with a left header, a right header a body and a standard footer.
Generic.xaml:
<Style TargetType="{x:Type local:FrameControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:FrameControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Border VerticalAlignment="Top" Height="50" Background="LightGray"/>
<DockPanel Background="{TemplateBinding HeaderColor}">
<ItemsControl ItemsSource="{TemplateBinding HeaderLeftContent}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel DockPanel.Dock="Left" FlowDirection="LeftToRight" HorizontalAlignment="Left"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ItemsControl ItemsSource="{TemplateBinding HeaderRightContent}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel DockPanel.Dock="Right" Height="40"
Margin="5, 5, 5, 0" VerticalAlignment="Top"
Orientation="Horizontal" FlowDirection="RightToLeft"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DockPanel>
<Border BorderThickness="1" BorderBrush="Black" Margin="0,49,0,0" Background="White">
<ItemsControl ItemsSource="{TemplateBinding BodyContent}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<DockPanel Background="White" Margin="5"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Border>
<Border VerticalAlignment="Bottom" Height="0" Background="LightGray"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
FrameControl.cs
public class FrameControl : Control
{
public Brush HeaderColor
{
get { return (Brush)GetValue(HeaderColorProperty); }
set { SetValue(HeaderColorProperty, value); }
}
public static readonly DependencyProperty HeaderColorProperty =
DependencyProperty.Register("HeaderColor", typeof(Brush), typeof(FrameControl), new UIPropertyMetadata(null));
public Collection<Object> HeaderLeftContent
{
get { return (Collection<Object>)GetValue(HeaderLeftContentProperty); }
set { SetValue(HeaderLeftContentProperty, value); }
}
public static readonly DependencyProperty HeaderLeftContentProperty =
DependencyProperty.Register("HeaderLeftContent", typeof(Collection<Object>), typeof(FrameControl),
new UIPropertyMetadata(null));
public Collection<Object> HeaderRightContent
{
get { return (Collection<Object>)GetValue(HeaderRightContentProperty); }
set { SetValue(HeaderRightContentProperty, value); }
}
public static readonly DependencyProperty HeaderRightContentProperty =
DependencyProperty.Register("HeaderRightContent", typeof(Collection<Object>), typeof(FrameControl),
new UIPropertyMetadata(null));
public Collection<Object> BodyContent
{
get { return (Collection<Object>)GetValue(BodyContentProperty); }
set { SetValue(BodyContentProperty, value); }
}
public static readonly DependencyProperty BodyContentProperty =
DependencyProperty.Register("BodyContent", typeof(Collection<Object>), typeof(FrameControl),
new UIPropertyMetadata(null));
static FrameControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(FrameControl), new FrameworkPropertyMetadata(typeof(FrameControl)));
}
public FrameControl()
{
SetValue(HeaderLeftContentProperty, new Collection<Object>());
SetValue(HeaderRightContentProperty, new Collection<Object>());
SetValue(BodyContentProperty, new Collection<Object>());
}
}
Usage example
<fc:FrameControl>
<fc:FrameControl.HeaderLeftContent>
<Label Content="Left content of the header"/>
</fc:FrameControl.HeaderLeftContent>
<fc:FrameControl.HeaderRightContent>
<Button>
<TextBlock Text="Example button"/>
</Button>
<ComboBox SelectedIndex="0">
<ComboBoxItem Content="Filter A"/>
<ComboBoxItem Content="Filter B"/>
<ComboBoxItem Content="Filter C"/>
</ComboBox>
</fc:FrameControl.HeaderRightContent>
<fc:FrameControl.BodyContent>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="name"/>
<TextBox Grid.Row="0" Grid.Column="2"/>
<Label Grid.Row="1" Grid.Column="0" Content="Type name"/>
<TextBox Grid.Row="1" Grid.Column="2" />
<Label Grid.Row="2" Grid.Column="0" Content="Keyword"/>
<TextBox Grid.Row="2" Grid.Column="2"/>
<Label Grid.Row="3" Grid.Column="0" Content="Parameter"/>
<ComboBox Grid.Row="4" Grid.Column="0" SelectedIndex="0">
<ComboBoxItem Content="Width"/>
<ComboBoxItem Content="Height"/>
</ComboBox>
<ComboBox Grid.Row="4" Grid.Column="1" SelectedIndex="0">
<ComboBoxItem Content="..."/>
<ComboBoxItem Content="="/>
<ComboBoxItem Content="<"/>
<ComboBoxItem Content=">"/>
</ComboBox>
<TextBox Grid.Row="4" Grid.Column="2"/>
</Grid>
</fc:FrameControl.BodyContent>
</fc:FrameControl>
I've tried using different type of properties to hold the elements for the different region like UIElementCollection but that made no difference. I also tried manually adding the controls to the logical tree, but I did not succeed in that. Am I missing something?
You say that your actual problem is that you cannot bind using ElementName with this control. Elements that are inside the FrameControl cannot be approached this way. However, that problem has absolutely nothing to do with the visual tree. Those elements are in the visual tree, but you just can't access them because they were defined inside a ControlTemplate.
You can use the ControlTemplate.FindName method to 'find' the elements for you. However, you need to have an element that the ControlTemplate has been applied to and a named element in the ControlTemplate:
// Assuming that your DockPanel in the ControlTemplate was named DockPanel
DockPanel dockPanel =
frameControl.Template.FindName("DockPanel", frameControl) as DockPanel;
if (dockPanel != null) // You must check for null
{
DoSomethingHereWith(dockPanel);
}
See the linked page for more help.

Categories