Reusing WPF layout XAML with Bindings? - c#

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.

Related

How to hide tab control when item source list is empty

I have a WPF .NET Core application with a TabControl bound to an ObservableCollection for the TabItems. I would like the TabControl to be hidden when the ObservableCollection becomes empty, and I would like to display another panel in its place. Then, when an item is added back to the ObservableCollection, I want the alternate panel hidden and the TabControl reshown. How would I accomplish this, hopefully in XAML with as little code-behind as possible? I know I can do it in code-behind.
Below is the key section of the app. I have hidden the TabControl, and included a Border control to represent the Panel that I will show when the TabControl is hidden.
<Window x:Class="TabTest.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:TabTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Border Name="emptyTabPanel" Grid.Row="1" BorderBrush="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}"
BorderThickness="1,1,1,1" Margin="5,0,5,5" Visibility="Hidden">
</Border>
<TabControl Name="MainTabControl" Visibility="Visible">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Header}" MinWidth="60"/>
<Button BorderThickness="0" Background="Transparent" Height="16" Width="15" Margin="15,2,0,0">
<Image Source="images/close.png" Height="8"/>
</Button>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
</Window>
using System.Windows;
using System.Collections.ObjectModel;
namespace TabTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<TabEntry> tabEntries;
public MainWindow()
{
InitializeComponent();
tabEntries = new ObservableCollection<TabEntry>();
MainTabControl.ItemsSource = tabEntries;
for (int i = 1; i <= 5; i++)
{
tabEntries.Add(new TabEntry { Header = "tab " + i });
}
}
}
public class TabEntry
{
public string Header { get; set; }
}
}
All ItemsControls provide a HasItems property that you can use in a Trigger. In contrast to a DataTrigger on ItemsSource.Count this also works when ItemsSource is not set at all.
<TabControl ...>
<TabControl.Style>
<Style TargetType="TabControl">
<Style.Triggers>
<Trigger Property="HasItems" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</TabControl.Style>
...
</TabControl>
You can do it with a DataTriger in a Style. Note that you need to remove Visibility="Visible" or the Setter won't be able to change it.
<TabControl Name="MainTabControl" Background="Red">
<TabControl.Style>
<Style TargetType="TabControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ItemsSource.Count, RelativeSource={RelativeSource Self}}" Value="0">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
<TabControl.ItemTemplate>
<!-- and so on -->
You need a proper view model for this, rather than binding the tabs directly to the collection. That view model would include a HasItems property which you'll bind your TabControl visibility to, and an inverse property - say IsEmpty - which you'll bind the panel's visibility to.
Bind ObservableCollection's events to listen to changes in item count and raise PropertyChanged events for your view model appropriately.

Attached property always has a default value in a style

I'm working with WPF and I want to use attached properties to work with some styling things in the validation of the controls (my example of the problem is really simple, binding a simple text).
This is my attached property:
public class ToolTipExtension
{
public static readonly DependencyProperty ShowToolTipProperty = DependencyProperty.RegisterAttached(
"ShowToolTip", typeof(string), typeof(ToolTipExtension), new PropertyMetadata("Deffault"));
public static void SetShowToolTip(DependencyObject element, string value)
{
element.SetValue(ShowToolTipProperty, value);
}
public static string GetShowToolTip(DependencyObject element)
{
return (string) element.GetValue(ShowToolTipProperty);
}
}
I have a simple style dictionary like this
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:at="clr-namespace:CarpetaTecnicaWPF.AttachedProperties"
>
<Style TargetType="{x:Type TextBox}" x:Key="Blah" >
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Grid>
<TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(at:ToolTipExtension.ShowToolTip)}" FontSize="50"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
As you can see, I'm trying to bind the Text property to my attached property.
In my Page, I'm using the style like this:
<TextBox Style="{StaticResource Blah}" at:ToolTipExtension.ShowToolTip="Prueba?"/>
The thing is, the value Prueba? does not appear. When I inspect the tree, I see this:
But in runtime, the result of the binding is Deffault
What am I doing wrong?
Your binding is incorrect.
The TemplatedParent in this case is not what you actually need. The ControlTemplate for the error is not applied to the text box itself, it's a stand-alone control template. So you are just getting a default value from a wrong FrameworkElement.
To access the text box your error template is applied to, you need to use the AdornedElementPlaceholder in your ControlTemplate. From that AdornedElementPlaceholder, you can access your text box via the AdornedElement property.
Here is an example:
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Grid>
<AdornedElementPlaceholder x:Name="adorner"/>
<TextBlock Text="{Binding ElementName=adorner, Path=AdornedElement.(at:ToolTipExtension.ShowToolTip)}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>

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?

Getting the control present inside ItemTemplate of a TabControl

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.

Issue with applying style on WPF UserControl

I have a user-control and I want to use it in some other project. There is no problem when I set some value to its properties directly:
<local:MyUserControl prop1="val1" prop2="val2">
...
</local:MyUserControl>
But I can't apply a style to it. I tried:
<Window ...>
<Window.Resources>
<Style x:Key="MyUserControlStyle" TargetType="{x:Type local:MyUserControl}">
<Setter Property="prop1" Value="val1"/>
<Setter Property="prop2" Value="val2"/>
</Style>
</Window.Resources>
<Grid>
<local:MyUserControl Style="{StaticResource ResourceKey=MyUserControlStyle}">
...
</local:MyUserControl>
</Grid>
</Window>
Where did I wrong? -Thanks
Using dear #Mario Vernari's instructions, I found it out that the problem was due to a bad strategy which I'd used to create my UserControl. I wanted to create a UserControl that be able to hold some other ones. So I had tried this:
<UserControl x:Class="MyNamespace.MyUserControl"
...
Style="{DynamicResource ResourceKey=MyUserControlStyle}">
<UserControl.Resources>
...
<Style x:Key="MyUserControlStyle" TargetType="{x:Type UserControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type UserControl}">
<Border BorderBrush="{Binding Path=DP1}">
...
<ContentPresenter ... Content="{TemplateBinding Content}"/>
...
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
</UserControl>
Where DP1 is a dependency property of type Brush. The UserControl which has been created through this way works if you set its properties (like DP1) directly. Absolutely this is not the true way as #Mario told me:
...When you use an UserControl, it means that you already know its layout, and there is no need to style the control further. You are defining its style twice at the same time thus results a collision...
And he added:
Instead, you should use a CustomControl; Define the default style in the Themes folder (if you own regular Visual Studio, it makes automatically). Afterward, you may override the default style in your own app. In the same way you would do for a normal base class and its derived.
Follow this:
http://www.codeproject.com/KB/WPF/WPFCustomControl.aspx ...
Obviously, in this case we need to derive our lookless control from ContentControl class (instead of Control class). You may take a look at this & this to master the details.
Here, I give thanks to #Mario again. ;)
You are giving Style="{StaticResource ResourceKey=MyUserControlStyle}".
It's just - Style="{StaticResource MyUserControlStyle}".

Categories

Resources