Getting the control present inside ItemTemplate of a TabControl - c#

I am developing a class derived from "Control" class to create a custom control. In the Generic.xaml file I have added the below code to create a Tab control template:
<Style TargetType="{x:Type local:NavigationPane}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:NavigationPane}">
<TabControl Name="tabControl" Margin="1" TabStripPlacement="Left">
<TabControl.ItemTemplate >
<DataTemplate >
<Button Name="buttonImage" >
<Image Name="img" Height="24" Width="24" Source ="{Binding PageIcon}"/>
</Button>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In the NavigationPane class I want to access control named "btnImage" and I want to add an event handler to it.
I tried the below approach:
ContentPresenter cp = tabControl.Template.FindName("PART_SelectedContentHost", tabControl) as ContentPresenter;
btnImage = tabControl.ContentTemplate.FindName("btnImage", cp) as Button;
if (btnImage != null)
{
btnImage.Click += btnImage_Click;
}
Looks like the above code can only get the items in the ContentTemplate not from ItemTemplate. I would like to know what is the approach to get the pointer to the control which is in Item Template, that is the header part of Tab Control.

Related

How to access UI control from code-behind of ResourceDictionary

So I have a ResourceDictionary to define my custom Window style. What I am struggling to do is to access controls from XAML file.
The ResourceDictionary looks like this
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="MyCustomWindowStyle" TargetType="{x:Type Window}">
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<WindowChrome CaptionHeight="30"/>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Grid>
<!-- the root window -->
<Border BorderThickness="0.3" BorderBrush="{DynamicResource GeneralDarkBlue}">
<AdornerDecorator>
<ContentPresenter />
</AdornerDecorator>
</Border>
<DockPanel Height="30" Background="{TemplateBinding Background}" VerticalAlignment="Top" LastChildFill="False">
<Viewbox x:Name="HamburgerMenu" DockPanel.Dock="Left" WindowChrome.IsHitTestVisibleInChrome="True">
<Viewbox.InputBindings>
<MouseBinding MouseAction="LeftClick" Command="{Binding SettingsClick}"/>
</Viewbox.InputBindings>
<Border Width="47" Height="32" Background="Transparent">
<Canvas>
<Path x:Name="TopbarIconHamburgerMenu" Margin="14,10" Data="M12.5,19h19.2v1H12.5V19z M12.5,13.7h19.2v1H12.5V13.7z M12.5,8.5h19.2v1H12.5V8.5z" Stretch="UniformToFill" Fill="#FFFFFF"/>
</Canvas>
</Border>
</Viewbox>
// the rest of viewboxes for minimize, maximize controls...
</DockPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And let's say I want to access the HamburgerMenu, so I do something like this
public partial class MyCustomWindowStyle : ResourceDictionary
{
public MyCustomWindowStyle()
{
InitializeComponent();
}
public void DoSomething()
{
var window = (Style)Application.Current.Resources["MyCustomWindowStyle"];
var hm = (Viewbox)window.Resources.FindName("HamburgerMenu");
}
}
and this returns null in the hm!
Any idea how to do this?
First of all, Style.Resource is a ResourceDictionary, and there are two important things to notice in the ResourceDictionary.FindName method documentation:
Summary section saying:
Not supported by this Dictionary implementation.
and Return Value section saying:
Always returns null.
Second of all, even if you tried to retrieve the ViewBox by key, it would have to be defined as a resource:
<Style x:Key="MyCustomWindowStyle" TargetType="{x:Type Window}">
<Style.Resources>
<ViewBox x:Key="HamburgerMenu" />
</Style.Resources>
</Style>
And it is not. It is a part of ControlTemplate's visual tree.
Third of all, ControlTemplate does not contain actual elements, but rather a recipe for creating them. So there's no actual ViewBox living inside the ControlTemplate to retrieve. Notice that ControlTemplate.FindName takes an additional parameter specifying an element for which the template was realized.
However, ControlTemplate does have a LoadContent method, which basically loads the visual tree defined by that template, and I think you could use it, and then invoke FindName on the root element. To simplify retrieval of the ControlTemplate let's first make it a resource:
<Style x:Key="MyCustomWindowStyle" TargetType="{x:Type Window}">
<Style.Resources>
<ControlTemplate x:Key="Template" TargetType="{x:Type Window}">
<Grid>
(...)
<ViewBox x:Key="HamburgerMenu" />
(...)
</Grid>
</ControlTemplate>
</Style.Resources>
<Setter Property="Template" Value="{StaticResource Template}" />
</Style>
Then this should do the trick for you:
var window = (Style)Application.Current.Resources["MyCustomWindowStyle"];
var template = (ControlTemplate)window.Resources["Template"];
var root = (FrameworkElement)template.LoadContent();
var hm = (ViewBox)root.FindName("HamburgerMenu");
Update
If your goal is to get hold of the ViewBox in an existing window with that template applied, first you need to know how to get hold of that particular window. It could be the Application.Current.MainWindow, otherwise you're highly likely to find it in the Application.Current.Windows collection. You could also implement the singleton pattern for that window, or use other methods like exposing a static property with reference to that window somewhere in your application, or using third-party tools, such as Service Locator in Prism.
Once you have the window in your hand, you only need to use the previously mentioned ControlTemplate.FindName method:
var window = (...);
var hm = (ViewBox)window.Template.FindName(name: "HamburgerMenu", templatedParent: window);
Note that accessing the resource dictionary in which the template was defined is not necessary.
As for why your attempts with previous solution failed - that's because ControlTemplate.LoadContent method yields freshly created element each time it is invoked, and modifying it does not reflect on elements previously created by that template.

MahApps MetroProgressBar does not work inside ControlTemplate

I would like to use MetroProgressBar in my UserControl.
<UserControl x:Class="WpfApplication7.UserControl1">
<StackPanel Background="#ccc">
<controls:MetroProgressBar IsIndeterminate="True"/>
</StackPanel>
</UserControl>
It works fine. But now I need to support external content in the user control.
So I created a new one "UserControl2" to demo:
<UserControl x:Class="WpfApplication7.UserControl2">
<UserControl.Resources>
<Style TargetType="{x:Type local:UserControl2}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:UserControl2}">
<StackPanel Background="#ccc">
<controls:MetroProgressBar IsIndeterminate="True"/>
<!--<ContentPresenter/>-->
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
</UserControl>
Then I put the both controls to the form:
<StackPanel>
<local:UserControl1 Background="#ccc"/>
<local:UserControl2 Background="#ccc" Margin="0,6,0,0"/>
</StackPanel>
As result I see that UserControl2 does not show Progress Bar.
How can I fix it?
Note: In the designer UserControl2 is rendered as expected with progress bar.
In your style for UserControl2, set properties EllipseDiameter and EllipseOffset to some value (default is 4), as shown below:
<Style TargetType="{x:Type local:UserControl2}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:UserControl2}">
<StackPanel Background="#ccc">
<controls:MetroProgressBar EllipseDiameter="12"
EllipseOffset="12"
IsIndeterminate="True"/>
<!--<ContentPresenter/>-->
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Elements of list that is bound in a ControlTemplate only rendered once

I have a control that is wrapping an Xceed DataGridControl (part of the Extended WPF Toolkit Community Edition). The control provides a simple property (without a backing dependency property) that can hold a list of buttons (field instantiated by the constructor):
public List<Button> GroupButtons
{
get { return groupButtons; }
set { groupButtons = value; }
}
The items of the property are then added in the XAML of a view that is using the control:
<local:CustomControl ...>
<local:CustomControl.GroupButtons>
<Button>foo<Button>
</local:CustomControl.GroupButtons>
...
</local:CustomControl ...>
I would like to render the buttons of this list inside the so-called "GroupHeaderControl" of the Xceed Datagrid, which is basically a grouping row like shown below:
To achieve this, I've overwritten the ControlTemplate of the GroupHeaderControl:
<ResourceDictionary ...>
<Style TargetType="{x:Type controls:CustomControl}">
<Style.Resources>
<Style TargetType="{x:Type xcdg:GroupHeaderControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type xcdg:GroupHeaderControl}">
<Border ...>
<StackPanel Height="{TemplateBinding Height}" Orientation="Horizontal">
<ContentPresenter />
<ItemsControl ItemsSource="{Binding GroupButtons, RelativeSource={RelativeSource AncestorType={x:Type controls:CustomControl}}}" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
</Style>
...
</ResourceDictionary>
Now here comes the problem: Instead of rendering the button(s) for each instance of the GroupHeaderControl, it is rendered only once. For illustration, imagine that in the image above only the button at the second group header ("Lyon") is visible while the other one ("Reims") is not.
The problem is apparently related to the fact that the items of the GroupButtons list are added via the XAML definition. If I hard code the items of the list, it works like a charm:
public List<Button> ButtonList
{
get { return new List<Button>()
{
new Button() { Content = "foo" }
}
}
I don't really get where this behavior is coming from. Does somebody have an idea?

Reusing WPF layout XAML with Bindings?

I've been stuck on trying to reuse layout code for my WPF application.
I'm trying to make an XML editor that lets you have multiple files open (via tabs).
My situation is as follows:
<TabControl>
<TabItem>
// Layout XAML with various {Binding} sources (File 1)
</TabItem>
<TabItem>
// Layout XAML with various {Binding} sources (File 2)
</TabItem>
<TabItem>
// Layout XAML with various {Binding} sources (File 3)
</TabItem>
</TabControl>
This works; however, each of the three TabItems is a huge chunk of copy & pasted code, with only a few names changed to avoid duplicate names.
I want to rewrite the code in such a way that something like this is possible:
<TabControl>
<TabItem>
// Reference to Template
</TabItem>
<TabItem>
// Reference to Template
</TabItem>
<TabItem>
// Reference to Template
</TabItem>
</TabControl>
And have a Template defined somewhere else.
I tried using a DataTemplate for the template, and assigning it to each TabItem with ContentTemplate, but although the layout displayed properly, all of the {Bindings} were lost.
I've googled extensively, but haven't been able to figure out how I should be approaching this.
I would greatly appreciate any links to demos that would show how to achieve binding without copy & pasting code.
I would also appreciate any tips for debugging failed bindings, other than trying things out until they work. (I'm comfortable debugging C# with the debugger, but not sure how to inspect XAML stuff)
Thanks in advance!
You should represent your tab items with an ObservableCollection, using the Window's ViewModel.
<TabControl ItemsSource="{Binding Path=TabItems, Mode=OneTime}" SelectedValue="{Binding Path=SelectedTab, Mode=TwoWay}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border Name="Border" Margin="0,0,-4,0" BorderThickness="1">
<ContentPresenter
x:Name="ContentSite"
HorizontalAlignment="Center"
Margin="12,2,12,2"
VerticalAlignment="Center"
ContentSource="Header"
RecognizesAccessKey="True"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
etc...
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
etc...
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
Each tab item would then be a view model itself, with all the data inside that you need to bind to for each tab. So for example:
public ObservableCollection<TabItemViewModel> TabItems
{
get
{
return m_SuspendTabItems;
}
private set
{
if (Equals(m_SuspendTabItems, value))
{
return;
}
m_SuspendTabItems = value;
NotifyPropertyChanged(s_SuspendTabItems);
}
}
Would be on your main WindowViewModel. To add a new tab, you would simply call TabItems.Add(new TabItemViewmodel());.
Where "TabItemViewModel" contains your binding for that particular tab item.
I would suggest to write a custom UserControl containing everything you now are copy-pasting, and add this UserControl into the tab items. Add to this UserControl your required sources as a Dependency Property. Now you can access it from your TabControl without loosing Bindings.
I made a quick non-working example:
MyControl.xaml
<UserControl Name=this>
<StackPanel>
<TextBox Text={Binding Something, ElementName=this} />
</StackPanel>
</UserControl>
MyControl.xaml.cs
public partial class MyControl : UserControl
{
public static readonly DependencyProperty SomethingProperty = DependencyProperty.Register("Something", typeof(string), typeof(MyControl));
public string KeyType
{
get { return (string)GetValue(SomethingProperty ); }
set { SetValue(SomethingProperty , value); }
}
}
program.xaml
<Window>
<TabControl>
<TabItem>
<MyControl Something={Binding Anything[0] />
</TabItem>
<TabItem>
<MyControl Something={Binding Anything[1] />
</TabItem>
//...
</TabControl>
</Window>
program.xaml.cs
//...
public string[] Anything { get; set; }
//...
Be aware that this is only a very simple example. You could easily add your required model as mentioned above to an ObservableCollection and generate the tab items from that automatically.

WPF TemplateBinding to DataContext of templated parent

We have four identical popups with grids in four XAML views. I'd like to move that XAML to a template and apply via a Style to to ContentControls in all four of them. The trouble is passing in the source of the items in the grids. We get that from each of four different view models. It's different in each case, the only thing that differs among the four cases. I'll probably end up renaming them consistently, but I'd like to think that's a separate issue.
Obviously I don't understand TemplateBinding at all. How do I bind a property of a child of the template to a property of the ContentControl that I'm applying the template to?
Except for the value of the DataSource attribute changing, the XAML for the grid is identical to what works perfectly well when we use it directly.
I added the TextBlock just to see if I could bind anything at all. I do get NaN there.
<Style x:Key="HistoryPopupContentStyle" TargetType="ContentControl">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{TemplateBinding Width,
diag:PresentationTraceSources.TraceLevel=High}"
Background="White"
Foreground="Black"/>
<dxg:GridControl
DataSource="{Binding RelativeSource={RelativeSource
Path=DataContext,
TraceLevel=High}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
>
<!-- Columns. The grid displays column headers
as desired but with no rows -->
</dxg:GridControl.Columns>
</dxg:GridControl>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Popup
Name="PopHistory"
DataContext="{Binding Path=HistoryList}"
>
<ContentControl DataContext="{Binding Path=HistoryList}"
Style="{StaticResource HistoryPopupContentStyle}"
Name="Testing"
/>
</Popup>
You will need to subclass ContentControl (or just Control), so that you can add new dependency properties.
public class GridControl : ContentControl
{
// TODO add dependency properties
public GridControl()
{
DefaultStyleKey = typeof(GridControl);
}
}
Add your "Items" dependency property to the above control (type IEnumerable).
Next, update your template to target the new type:
<Style x:Key="HistoryPopupContentStyle" TargetType="local:GridControl">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<dxg:GridControl ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=local:GridControl},Path=Items}" />
Alternately, you could set the "Template" instead of the "ContentTemplate". This would be when you use TemplateBinding:
<Style x:Key="HistoryPopupContentStyle" TargetType="local:GridControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:GridControl">
<dxg:GridControl ItemsSource="{TemplateBinding Items}" />
Use it by binding the Items property to your source items:
<local:GridControl Style="{StaticResource HistoryPopupContentStyle}"
Items="{Binding Path=HistoryList}" />
You could also skip creating a subclass altogether, and just use the Content property of ContentControl to stash the items:
<Style x:Key="HistoryPopupContentStyle" TargetType="ContentControl">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<dxg:GridControl ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=local:GridControl},Path=Content}" />
Or using the Template / TemplateBinding approach
<Style x:Key="HistoryPopupContentStyle" TargetType="ContentControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<dxg:GridControl ItemsSource="{TemplateBinding Content}" />
Use like this:
<ContentControl Style="{StaticResource HistoryPopupContentStyle}"
Content="{Binding Path=HistoryList}" />

Categories

Resources