Closable tab with tab item being a user control - c#

I am working on a project in C# WPF. I have a tab container and I want to dynamically load different types of tabs into the tab container as the user requires. As an example I am doing something like the following:
tabContainer.Items.Add(new MyUserControl());
I want each tab to have a close button so the tab can be removed the container when the user no longer requires it.
I found this code project example but from what I can see you are a loading a user control which contains the xaml for the tab itself, not the tab content or am I missing something.
How can I load in my User Control into the tab container, but also have the tab closable.
Currently the tab that I am loading in uses some static binding to set the tab title using the following:
<TabControl x:Name="tabContainer" Grid.Column="2" Margin="10,45,0,0" RenderTransformOrigin="0.5,0.55" Grid.ColumnSpan="3">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
</Style>
</TabControl.Resources>
</TabControl>
My user control then has a `public string TabHeader{get;set;} which gets set in the constructor depending on what constructor of my user control is used.

You will have to define the close Button yourself. You could for example do this in the HeaderTemplate of the TabItem:
<TabControl x:Name="tabContainer">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<Button Content="x" Click="Button_Click_2"
Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType=TabItem}}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
</TabControl>
The Tag property is bound to the UserControl in the Items collection which you can remove in the click event handler of the Button, like this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
tabContainer.Items.Add(new MyUserControl());
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
tabContainer.Items.Remove(button.Tag);
}
}

If you want to add a close button to each tab, that would be in the TabItem style ControlTemplate. Normally you'd specify the data context (i.e. the data only that's driving the content) in Content and then specify the look in ContentTemplate. If your Content is a UserControl then you don't specify the ContentTemplate since a UserControl knows how to draw itself.

For my sins, I've added close-tab buttons to the WPF TabControl. I ended up putting the close button in the ItemTemplate. Here's a minimal version that works with the way you're populating the TabControl and the header content:
<TabControl
>
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
</Style>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Content="{Binding}"
Grid.Column="0"
/>
<Button
VerticalAlignment="Center"
Grid.Column="1">
<Path
Data="M 0, 0 L 12, 12 M 12,0 L 0,12"
Stroke="Red"
StrokeThickness="2"
Width="12"
Height="12"
/>
</Button>
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
<local:UserControl1 TabHeader="First Item" />
<local:UserControl1 TabHeader="Second Item" />
</TabControl>

Related

Launch MouseEnter event Image from XAML dictionnary with MVVM

I have a template for a DataGrid located in a ResourceDictionary.
BlockStyles.xaml
<Style TargetType="DataGrid" x:Key="SearchExpGrid">
<Setter Property="AlternatingRowBackground" Value="#4C87C6ff"/>
<Setter Property="GridLinesVisibility" Value="None"/>
<Setter Property="FontSize" Value="15"/>
<Setter Property="HeadersVisibility" Value="Column"/>
<Setter Property="CellStyle">
<Setter.Value ... />
</Setter>
<Setter Property="RowStyle">
<Setter.Value ... />
</Setter>
<Setter Property="ColumnHeaderStyle">
<Setter.Value>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border
x:Name="Border" Background="White" BorderBrush="#4C87C6" BorderThickness="1" >
<StackPanel Orientation="Horizontal" Margin="5,5" HorizontalAlignment="Center">
<TextBlock
x:Name="TxtB" Text="{Binding}"
Foreground="#4C87C6" FontWeight="DemiBold" HorizontalAlignment="Center"/>
<Image Source="../Images/dropdown.png"
Width="10" Height="10" Margin="5,0,5,0"
MouseEnter="DropdownButton_MouseEnter"
MouseLeave="DropdownButton_MouseLeave"
MouseEnter="DropdownButton_Click"/>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#4C87C6"/>
<Setter TargetName="TxtB" Property="Foreground" Value="White"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
I always have an error because the function can't find it's definition.
I first implemented the function in the View file where the style is used but it doesn't work.
I tried this method from StackOverflow using a resource class inheriting ResourceDictionnary but got the same error.
I then tried to use ICommand and RelayCommand to execute the function from the ViewModel but didn't got any result.
I also didn't find where I could add an EventHandler ImgDropdownButton.MouseEnter += new MouseEventHandler(MouseEnter_DropdownButton); using MVVM.
Is there a better solution for this kind of behaviour or if adding an EventHandler is the best solution, where sould be the best place to add it ?
Thanks in advance
Edit :
I managed to handle the function using a code-behind file for my ResourceDictionary following this.
BlockStyles.xaml.cs
private void DropdownButton_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
Mouse.OverrideCursor = Cursors.Hand;
}
private void DropdownButton_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
Mouse.OverrideCursor = Cursors.Arrow;
}
private void DropdownButton_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
//Function to show the popup
}
The MouseEnter and MouseLeave function are working, but I don't understand how to use the function to make my popup appear.
What I'm trying to do is that when I click on the Dropdown Image on the column header, I want to display a Popup, like an Excel one. This will allow the user to filter the columns values.
The file where my Grid and Popup are : (SearchExpView.xaml)
<Grid Grid.Row="2" Grid.Column="1">
<searchcomponents:ExpListView x:Name="ExpDatagrid"
DataContext="{Binding OExpListVM}"
Width="auto" Height="auto"/>
</Grid>
<Popup x:Name="PopupFiltre">
PopupFiltre content
</Popup>
Definition of my Datagrid : (ExpListView.xaml)
<Grid>
<DataGrid x:Name="ExpGrid" Style="{StaticResource SearchExpGrid}"
BorderThickness="0" BorderBrush="#4C87C6"
HorizontalAlignment="Left" VerticalAlignment="Top"
MinHeight="200" Height="auto" Margin="10,10,0,0"
MinWidth="780" Width="auto"
ItemsSource="{Binding}" DataContext="{Binding tableExpertise.DefaultView}"
AutoGenerateColumns="True" CanUserAddRows="False" IsReadOnly="True">
<DataGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick"
Command="{Binding DataContext.OnRowDoubleClickedCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding ElementName=ExpGrid, Path=CurrentItem}"/>
</DataGrid.InputBindings>
<DataGrid.ContextMenu>
<ContextMenu Name="dgctxmenu">
<Separator></Separator>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</Grid>
I'm looking for a way to be able to implement this popup fonction but I can't find out how to link everything together.
My Window is SearchExpView.xaml (with the Datagrid and the Popup). My Datagrid component is defined in ExpListView.xaml and styled in BlockStyles.xaml, which is not a window. I want to make the Popup (in SearchExpView.xaml) visible by clicking on the dropdown button (defined in BlockStyles.xaml)
Then you need to get a reference to the Popup in the window from the ResourceDictionary somehow.
You could for example use the static Application.Current.Windows property:
var window = Application.Current.Windows.OfType<SearchExpView>().FirstOrDefault();
if (window != null)
window.PopupFiltre.IsOpen = true;
Also make sure that you make the Popup accessible from outside the SearchExpView class:
<Popup x:Name="PopupFiltre" x:FieldModifier="internal">
...

Customize nested control inside a BasedOn style

I'm approaching to WPF (in particular I'm working on MVVM) and I'm trying to creating a custom Window style that will be used throughout the application. What I want to do is create a base style that will define the window color, the border, icon, title, etc. The Window can be resizable or dialog like, so I used WindowChrome to set up the "resizable window" that has minimize, maximize and close buttons by default and that's, in fact, resizable. For the Login window I would like to have a window that uses the base style, but the user can't resize or maximize it, so the maximize button should not be visible at all. I've been working on BasedOn styles and I can override properties succesfully, but I can't manage to define which buttons can or can't be visible inside the window. So what I'm trying to do is changing a nested UI control (a StackPanel in this case).
Here is the base style I created, that contains, for now, all the window properties and the window buttons as well (I tried to comment it the best I could):
<ControlTemplate TargetType="{x:Type Window}" x:Key="DefaultWindowsTemplate">
<!-- The outer border of the Window -->
<Border Padding="{Binding OuterMarginSizeThickness, FallbackValue=10}">
<!-- The inner border of the Window and the Window itself, from the contour line to the shadow -->
<Grid>
<Border CornerRadius="{Binding WindowCornerRadius}"
BorderBrush="{StaticResource AlizarinBrush}"
BorderThickness="{Binding OutlineBorderThickness, FallbackValue=1}"
Background="{StaticResource VoidnessBrush}">
<Border.Effect>
<DropShadowEffect Color="{StaticResource Voidness}" ShadowDepth="0" Opacity="1"/>
</Border.Effect>
</Border>
<!-- The Container grid, composed by the title bar and the content area -->
<Grid>
<!-- Rows definition -->
<Grid.RowDefinitions>
<RowDefinition Height="{Binding TitleHeight, FallbackValue=30}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Title bar row that contains icon, title and window buttons -->
<Grid Margin="{Binding TitleHeightMargin}"
Background="{StaticResource VoidnessBrush}"
Grid.Row="0"
Panel.ZIndex="1"
>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Window icon -->
<Button Margin="5 5 0 0"
Style="{StaticResource WindowIconButtonStyle}"
Command="{Binding MenuCommand}">
<Image Source="/Images/Logos/khm_logo_titlebar.png"/>
</Button>
<!-- Window title -->
<TextBlock Grid.Column="1"
Foreground="{StaticResource ConcreteBrush}"
Margin="15 5 0 0"
TextAlignment="Center"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Title, FallbackValue='Window Title'}"/>
<!-- Window buttons - THIS IS THE CONTROL I WANT TO DEFINE INSIDE 'BASED ON' STYLES, WHERE I WILL NOT HAVE THE MAXIMIZE BUTTON -->
<StackPanel Grid.Column="2"
Orientation="Horizontal">
<Button Style="{StaticResource WindowButtonsStyle}"
Content="0"
Command="{Binding MinimizeCommand}"/>
<ToggleButton Style="{StaticResource MaximizeWindowButtonStyle}"
Command="{Binding MaximizeCommand}"/>
<Button Style="{StaticResource WindowCloseButtonStyle}"
Content="r"
Command="{Binding CloseCommand}"/>
</StackPanel>
</Grid>
<!-- The Window content -->
<Grid Margin="1 5 0 0" Grid.Row="1">
<ContentPresenter/>
</Grid>
</Grid>
</Grid>
</Border>
</ControlTemplate>
<Style TargetType="Window" x:Key="DefaultWindowsStyle">
<Setter Property="Template" Value="{StaticResource DefaultWindowsTemplate}"/>
<Setter Property="MinWidth" Value="{Binding WindowMinWidth}"/>
<Setter Property="MinHeight" Value="{Binding WindowMinHeight}"/>
<Setter Property="WindowStyle" Value="None"/>
<Setter Property="AllowsTransparency" Value="True"/>
</Style>
Then I start editing the base style as following (in another XAML file of course):
<Style TargetType="Window" x:Key="DialogWindowsStyle" BasedOn="{StaticResource DefaultWindowsStyle}">
<!-- REMOVE THE MAXIMIZE BUTTON INSIDE THE NESTED STACK PANEL -->
</Style>
So what is the right way to edit part of the UI while using the same style?
Thank you in advance for the help.
I would define boolean attached properties local:WindowExt.ShowMaximizeButton etc. with default values of true. In the ControlTemplate I'd apply those to the buttons with TemplateBindings, and set them via style setters (or directly on Window elements in XAML).
Here's an example of ShowMaximizeButton; the others are just the same thing with different names. When you copy and paste the dependency property definition, be careful you update the property name every place it appears. I use snippets to create those, to minimize careless errors.
public static class WindowExt
{
public static bool GetShowMaximizeButton(Window obj)
{
return (bool)obj.GetValue(ShowMaximizeButtonProperty);
}
public static void SetShowMaximizeButton(Window obj, bool value)
{
obj.SetValue(ShowMaximizeButtonProperty, value);
}
public static readonly DependencyProperty ShowMaximizeButtonProperty =
DependencyProperty.RegisterAttached("ShowMaximizeButton", typeof(bool), typeof(WindowExt),
new PropertyMetadata(true));
}
Make sure this is somewhere prior to where the window control template is defined, in the same resource dictionary:
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
The maximize button in the window control template. There won't be any change to the control template other than adding these appropriately bound visibility attributes to the buttons. Note the parens in the Binding's Path; those are critical because it's a multi-part identifier for a single property.
<ToggleButton
Style="{StaticResource MaximizeWindowButtonStyle}"
Command="{Binding MaximizeCommand}"
Visibility="{TemplateBinding local:WindowExt.ShowMaximizeButton, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
And usage in the window style:
<Style TargetType="Window">
<Setter Property="local:WindowExt.ShowMaximizeButton" Value="True" />
</Style>
Note that attached properties are dependency properties of the control itself, nothing to do with any DataContexts or viewmodels.

DataTemplate Binding to multiple object properties

I'm using button styling, with data template, and having problem with showing text (Name and Number properties of my Table objects).
This is xaml:
<Style x:Key="btnTable" TargetType="{x:Type Button}" >
<Setter Property="Background" Value="Transparent" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type my:Tables}">
<Grid>
<Image VerticalAlignment="Center" Source="/Images/Ico/table-40x40.png" />
<TextBlock Text="{Binding Path=Naziv}" />
<Ellipse Canvas.Top="30" Canvas.Left="30" Fill="#FF6A4E8C" />
<TextBlock Text="{Binding Number}" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Then, in .cs I create buttons like this:
Button b = new Button();
Style myStyle = (Style)Resources["btnTable"];
b.Style = myStyle;
b.DataContext = myTableItem;
If I test buttons click event, it has in DataContext property valid Table object.
I have tried replacing Binding Path width Binding Path=DataContext.Name, adding RelativeSource=Self, but nothing helps.
Additionally, I tried creating buttons using ItemsControl, but have the same issue.
b.Content = myTableItem;
It's the ContentTemplate. It's a template for the Content.

WPF ListBoxItem ControlTemplate breaks some MouseDown/Selection

I have an issue with ListBoxItems. I am trying to make all controls in the ListBoxItem select it as well, so clicking on a TextBox, Label, etc will select the ListBoxItem. Pretty simple so far.
I am also changing the ListBoxItem Template to change the selection visualization from highlighting the background to just drawing a border. Also pretty simple.
The combination of these two, however, seems to cause some really irritating issues with MouseDown and PreviewMouseDown, specifically in my case regarding Labels in a Grid, where one creates a "void" occupied by Grid space.
Using snoop, I can see the PreviewMouseDown event stopping at the ScrollViewer inside the ListBox, and not going all the way to the ListBoxItem.
XAML:
<Window x:Class="ListBoxClickThroughTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="525"
Height="350">
<Grid>
<ListBox ItemsSource="{Binding Items}"
SelectionMode="Single">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Name="VerySuperLongLabel"
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Left"
Content="VerySuperLongLabel"
Padding="0" />
<TextBox Name="Textbox1"
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Right"
Text="Textbox1 Text" />
<Label Name="ShortLabel"
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Left"
Content="ShortLabel"
Padding="0" />
<TextBox Name="Textbox2"
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Right"
Text="Textbox2 Text" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="PreviewMouseDown"
Handler="ListBoxItem_PreviewMouseDown" />
<EventSetter Event="MouseDown"
Handler="ListBoxItem_PreviewMouseDown" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="Bd"
BorderThickness="1">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Bd" Property="BorderBrush" Value="Gray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</Window>
Code-behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace ListBoxClickThroughTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
Items = new List<string>() { "1", "2" };
InitializeComponent();
DataContext = this;
}
public List<string> Items { get; set; }
private void ListBoxItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var listBoxItem = (ListBoxItem)sender;
listBoxItem.IsSelected = true;
}
}
}
However, if I remove the Template setter, all is well. Is there some magic in the template I'm missing? I tried renaming the border to "Bd" as that was what the default template border was named, but no luck. Any ideas?
If you change the horizontal alignment of the labels from "Left" to "Stretch" this will fix the problem and keep the visual formatting the same.
Mousedown events only work in areas where elements exist. By having the labels at a "left" horizontal alignment, you are creating the "void" you mentioned, where no element exists at that level that can be clicked. To visually see the difference, try temporarily setting the background property of the label elements that are giving you problems, and you'll see the element doesn't extend all the way to the textbox.

Putting a ContentControl *inside* a WPF DataTemplate?

I have a custom Expander control called SpecialExpander. It is basically just a standard Expander with a fancy header and a couple properties (HeaderText and IsMarkedRead).
I began by creating a simple class:
public class SpecialExpander : Expander
{
public string HeaderText { get; set; }
public bool IsMarkedRead { get; set; }
}
Then I created a style that sets a couple properties on the expander (e.g., margins, padding, etc.) and, importantly, it also defines a custom DataTemplate for the HeaderTemplate property. The template is basically a grid with two rows.
As shown in the illustrations below...
for the top row, I'd like a fixed layout (it's always TextBlock TextBlock CheckBox)
for the bottom row, however, I want to be able to provide custom XAML for each expander.
I tried putting <ContentControl Grid.Row="1" ... /> in the DataTemplate, but I couldn't figure out how to hook it up properly.
alt text http://img85.imageshack.us/img85/1194/contentcontrolwithintem.jpg
Question
How can I build a DataTemplate for my SpecialExpander so that the header has some fixed content (top row) and a place-holder for custom content (bottom row)?
For the second illustration, I would want to be able to do something like this:
<SpecialExpander HeaderText="<Expander Header Text>" IsMarkedRead="True">
<SpecialExpander.Header>
<StackPanel Orientation="Horizontal">
<RadioButton Content="High" />
<RadioButton Content="Med" />
<RadioButton Content="Low" />
</StackPanel>
<SpecialExpander.Header>
<Grid>
<Label>Main Content Goes Here</Label>
</Grid>
</SpecialExpander>
It hit me this morning how to solve this: instead of building a SpecialExpander, I just need a normal Expander. Then, for the header, I will use a custom ContentControl called SpecialExpanderHeader.
Here's how it works...
SpecialExpanderHeader class:
public class SpecialExpanderHeader : ContentControl
{
public string HeaderText { get; set; }
public bool IsMarkedRead { get; set; }
}
SpecialExpanderHeader style:
<Style TargetType="custom:SpecialExpanderHeader">
<Setter Property="Padding" Value="10" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="custom:SpecialExpanderHeader">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="5" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=custom:SpecialExpanderHeader}, Path=HeaderText}" />
<CheckBox Margin="100,0,0,0" IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=custom:SpecialExpanderHeader}, Path=IsMarkedRead}" />
</StackPanel>
<Separator Grid.Row="1" />
<ContentPresenter Grid.Row="2" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Expander style
<Style x:Key="Local_ExpanderStyle" TargetType="Expander" BasedOn="{StaticResource {x:Type Expander}}">
<Setter Property="Margin" Value="0,0,0,10" />
<Setter Property="Padding" Value="10" />
<Setter Property="FontSize" Value="12" />
</Style>
Usage
<Expander Style="{StaticResource Local_ExpanderStyle}">
<Expander.Header>
<custom:SpecialExpanderHeader IsMarkedRead="True" HeaderText="Test">
<StackPanel Orientation="Horizontal">
<RadioButton Content="High" />
<RadioButton Content="Medium" />
<RadioButton Content="Low" />
</StackPanel>
</custom:SpecialExpanderHeader>
</Expander.Header>
<Grid>
<!-- main expander content goes here -->
</Grid>
</Expander>

Categories