Closing TabItem by clicking middle button - wpf

I have a problem.
In my WPF application, if i press a tabItem with middle mouse button, this tabItem should close. Just like in FireFox.
But I try to do this using MVVM, and i need to use commands. Also my tabItems are created dynamically.
Help me plz!
Thank you!

Create a DataTemplate for your tab items like this:
<DataTemplate x:Key="ClosableTabTemplate">
<Border>
<Grid>
<Grid.InputBindings>
<MouseBinding Command="ApplicationCommands.Close" Gesture="MiddleClick" />
</Grid.InputBindings>
<!-- the actual contents of your tab item -->
</Grid>
</Border>
</DataTemplate>
In your application window, add a the close command
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Close" Executed="CloseCommandExecuted" CanExecute="CloseCommandCanExecute" />
</Window.CommandBindings>
and finally assign the data template as item template to your tab control.

You could add a MouseBinding to some InputBindings perhaps?

Closing a TabItem (Not Selected) - TabControl
<TabControl x:Name="TabControlUser" ItemsSource="{Binding Tabs}" Grid.RowSpan="3">
<TabControl.Resources>
<Style TargetType="TabItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<Border Name="Border" BorderThickness="1,1,1,0" BorderBrush="Gainsboro" CornerRadius="4,4,0,0" Margin="2,0">
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="10,2"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="LightSkyBlue" />
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter TargetName="Border" Property="Background" Value="GhostWhite" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Header}" FontWeight="ExtraBold" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Image Source="/Images/RedClose.png" Width="22" Height="22" MouseDown="Image_MouseDown" HorizontalAlignment="Right" Margin="10 0 0 0"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<!-- this is the body of the TabItem template-->
<DataTemplate>
<UserControl Content="{Binding UserControl, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
C# Code - Image MouseDown Click Event
try
{
int matches = 0;
DependencyObject dependency = (DependencyObject)e.OriginalSource;
// Traverse the visual tree looking for TabItem
while ((dependency != null) && !(dependency is TabItem))
dependency = VisualTreeHelper.GetParent(dependency);
if (dependency == null)
{
// Didn't find TabItem
return;
}
TabItem selectedTabItem = dependency as TabItem;
var selectedTabContent = selectedTabItem.Content as MainWindowViewModel.TabItem;
foreach (var item in MainWindowViewModel.Tabs)
{
if (item.Header == selectedTabContent.Header)
{
matches = MainWindowViewModel.Tabs.IndexOf(item);
}
}
MainWindowViewModel.Tabs.RemoveAt(matches);
}
catch (Exception ex)
{
System.Windows.Application.Current.Dispatcher.Invoke((System.Action)(() => new View.MessageBox(ex.Message).ShowDialog()));
}
This event finds with the exact Mouse clicked position (as TabItem)and this will remove
MainWindowViewModel.Tabs.RemoveAt(matches); the TabItem(even if it is not selected).

Related

Editing the property of an element within the ControlTemplate in WPF

I have some radio buttons that I'm building a custom control template for. Image of the buttons:
In the control template, each radio button will have a textblock with its name and another textblock below it to indicate if it's unavailable.
I want the "Unavailable" text to be visible ONLY when the button is NOT enabled. When the radio button is ENABLED, the "Unavailable" textblock should be collapsed.
Here is the simplified view.xaml for the buttons:
<RadioButton Name="one"
IsEnabled="{Binding One_isAvailable}"
Style="{StaticResource RadioButtonTheme}" />
<RadioButton Name="two"
IsEnabled="{Binding Two_isAvailable}"
Style="{StaticResource RadioButtonTheme}" />
<RadioButton Name="three"
IsEnabled="{Binding Three_isAvailable}"
Style="{StaticResource RadioButtonTheme}"/>
Here is the simplified version of the styling I have so far (RadioButtonTheme.xaml):
<ResourceDictionary>
<Style BasedOn="{StaticResource {x:Type ToggleButton}}"
TargetType="{x:Type RadioButton}"
x:Key="RadioButtonTheme">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border CornerRadius="7">
<StackPanel VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center"
Text="{TemplateBinding Property=Name}"
Foreground="{TemplateBinding Property=Foreground}">
</TextBlock>
<TextBlock Name="UnavailableTextBlock"
HorizontalAlignment="Center"
Text="Unavailable"
FontSize="14"
FontStyle="Italic"
Foreground="{TemplateBinding Property=Foreground}">
</TextBlock>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
So I've tried setting a couple things:
I set a visiblitity property on the radio button on the view.xaml. I then binded that visibility to the "UnavailableTextBlock" in the radiobuttontheme.xaml and set the rest of the template visiblity to "Visible." I thought that I can leave the template visible except for one element of it. I now don't think that's possible.
I tried directly binding the "UnavailableTextBlock" to the IsEnabled property of the radiobutton, and ran it through a BoolToVisiblityConverter.
<TextBlock Name="UnavailableTextBlock"
Visibility="{TemplateBinding Property=IsEnabled, Converter={StaticResource BoolToVisConverter}}">
However, I can't seem to get my converter to work inside of the ResourceDictionary. The program will crash with the error: "Cannot find resource named 'BoolToVisConverter'. Resource names are case sensitive"
I have this converter working across my other xaml files since I added it to my <Application.Resources> in the app.xaml. Do I need to link my Resource dictionary to the converter? How do I do that? <ResourceDictionary.Resources> didn't seem to work for me.
I tried adding a datatrigger to the "UnavailableTextBlock" as below:
<TextBlock Name="UnavailableTextBlock"....>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{TemplateBinding Property=IsEnabled}" Value="false">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
However, I get an error saying: '"IsEnabled" member is not valid because it does not have a qualifying type name.'
I'm guessing that it's referencing the IsEnabled property of the TextBlock and not of the radio button? Although I'm not too sure. I'm still learning WPF.
Thanks for all your help in advance!
If I understand correctly what you want to implement, then you need to use the control template trigger.
<Style BasedOn="{StaticResource {x:Type ToggleButton}}"
TargetType="{x:Type RadioButton}"
x:Key="RadioButtonTheme">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Border CornerRadius="7">
<StackPanel VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center"
Text="{TemplateBinding Property=Name}"
Foreground="{TemplateBinding Property=Foreground}">
</TextBlock>
<TextBlock Name="UnavailableTextBlock"
HorizontalAlignment="Center"
Text="Unavailable"
FontSize="14"
FontStyle="Italic"
Foreground="{TemplateBinding Property=Foreground}">
</TextBlock>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="UnavailableTextBlock" Property="Visibility" Value="Hidden"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Button foreground doesn't change when content is StackPanel

I have a button style defined where the template has a ContentPresenter with a name of "contentPresenter". It then has a trigger set up like this:
<ControlTemplate.Triggers>
<Trigger Property="UIElement.IsEnabled" Value="False">
<Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="#FF838383" />
</Trigger>
</ControlTemplate.Triggers>
This trigger simply changes the foreground color of a button to gray when the button is disabled. However, I have one button in my application which does not have simple text as its content. The button looks like this:
<Button Grid.Column="3" Grid.Row="3" Grid.ColumnSpan="2"
Margin="120 20 30 10"
Command="{Binding SomeCommand}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding MyImage}" Margin="0 0 2 0"
Visibility="{Binding ShowMyImage, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock Text="{Binding ButtonText}" />
</StackPanel>
</Button>
The foreground of this button is always the same whether the button is disabled or not. I'm thinking it's because the text for the button is in a textblock within the stackpanel in the content presenter, but I don't know how to get the foreground color changes to be applied to the inner textblock.
I tried changing the Property in the trigger to use "TextBlock.Foreground" instead of "TextElement.Foreground". I also tried binding the Foreground of the inner textblock to the foreground color of the closest ancestor of type FrameworkElement like this:
<TextBlock Text="{Binding ButtonText}" Foreground="{Binding RelativeSource={RelativeSource AncestorType={x:Type FrameworkElement}}, Path=(FrameworkElement.Foreground)}" />
None of this worked. What do I need to do to get the foreground color to apply to the TextBlock inside the StackPanel?
Your trigger should be in the Style, and should be setting Foreground on the Button itself, rather than on the ContentPresenter. Then it'll Just Work, with a custom template or with the default template. No need for any extra bindings.
<Style TargetType="Button" x:Key="MyButtonStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="Beige" BorderBrush="Black" BorderThickness="1" Padding="6,2">
<ContentPresenter
/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="#FF838383" />
</Trigger>
</Style.Triggers>
</Style>
Usage:
<Button
IsEnabled="False"
Style="{StaticResource MyButtonStyle}"
>
<StackPanel>
<TextBlock
>Blah Blah</TextBlock>
<Button>X</Button>
</StackPanel>
</Button>
<Button Style="{StaticResource MyButtonStyle}" IsEnabled="False">Testing</Button>
Screenshot (never mind my ugly template):

How can I vary the layout of a UserControl by a Property?

I've made the smallest project I can to demonstrate the problem:
Code behind:
public partial class AxisControl : UserControl
{
public static readonly DependencyProperty LayoutProperty =
DependencyProperty.Register("Layout", typeof(Orientation), typeof(AxisControl),
new PropertyMetadata(Orientation.Horizontal));
public Orientation Layout
{
get { return (Orientation)GetValue(LayoutProperty); }
set { SetValue(LayoutProperty, value); }
}
public AxisControl()
{
InitializeComponent();
}
}
Xaml:
<UserControl.Resources>
<ContentControl x:Key="horizontalLayout" Height="60">
<TextBlock Text="{Binding Layout, RelativeSource={RelativeSource AncestorType=UserControl}}" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</ContentControl>
<ContentControl x:Key="verticalLayout" Width="60">
<TextBlock Text="{Binding Layout, RelativeSource={RelativeSource AncestorType=UserControl}}" HorizontalAlignment="Left" VerticalAlignment="Center">
<TextBlock.LayoutTransform>
<RotateTransform Angle="-90"/>
</TextBlock.LayoutTransform>
</TextBlock>
</ContentControl>
</UserControl.Resources>
<UserControl.Style>
<Style TargetType="UserControl">
<Style.Setters>
<Setter Property="Content" Value="{StaticResource horizontalLayout}"/>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding Layout, ElementName=root}" Value="Vertical">
<Setter Property="Content" Value="{StaticResource verticalLayout}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
Edit: Xaml now includes the elements I want to arrange in the UserControl.
MainWindow Xaml:
<Grid>
<local:AxisControl Layout="Vertical"/>
</Grid>
The idea is to set the layout of the UserControl according to its Layout property so I put both layouts in static resources and made a Style to set the Content to the one I want according to Layout which is of type Orientation.
Edit: I want the Content to include elements arranged in a different order according to the Orientation.
The UserControl displays correctly but there's an error in the output window that concerns me:
Cannot find source for binding with reference 'RelativeSource
FindAncestor, AncestorType='System.Windows.Controls.UserControl',
AncestorLevel='1''. BindingExpression:Path=Layout; DataItem=null;
target element is 'TextBlock' (Name=''); target property is 'Text'
(type 'String')
Does this mean it's trying to do a Binding before it's in the visual tree possibly from the Trigger?
Note the use of RelativeSource and ElementName in the Bindings as it's incorrect to set the DataContext at the root of a UserControl because it breaks the DataContext inheritance.
What am I doing wrong and how can I get rid of the error?
Inspired by Clemens comment and further research I realized I needed a ControlTemplate resource for each layout and not resources containing instances of the elements themselves.
<UserControl.Resources>
<ControlTemplate x:Key="horizontalLayout">
<Border Height="60" Background="LightBlue">
<TextBlock Text="{Binding Layout, RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</Border>
</ControlTemplate>
<ControlTemplate x:Key="verticalLayout" TargetType="UserControl">
<Border Width="60" Background="LightBlue">
<TextBlock Text="{Binding Layout, RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="Left" VerticalAlignment="Center">
<TextBlock.LayoutTransform>
<RotateTransform Angle="-90"/>
</TextBlock.LayoutTransform>
</TextBlock>
</Border>
</ControlTemplate>
</UserControl.Resources>
<UserControl.Style>
<Style TargetType="UserControl">
<Style.Setters>
<Setter Property="Template" Value="{StaticResource horizontalLayout}"/>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding Layout, ElementName=root}" Value="Vertical">
<Setter Property="Template" Value="{StaticResource verticalLayout}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
Layout = Horizontal
Layout = Vertical

Dynamically change the content of of control based on a value

I want to achieve the following behavior:
Depending on a value use a different datatemplate:
<DataTemplate x:Key="cardTemplate2">
<Border x:Name="container">
.....
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ShowSecondDT}" Value="True">
<Setter Property="Child" TargetName="container">
<Setter.Value>
<StackPanel Orientation="Vertical" >
</StackPanel>
</Setter.Value>
</Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
The application fails claiming that Setter Property="Child" is null...
Another information is that this Datatemplate in the resource of a control: (devexpress gris)
<dxg:GridControl xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
x:Name="gridTitulaire" DataSource="{Binding Contacts}" Width="600" >
<dxg:GridControl.Resources>
<DataTemplate x:Key="cardTemplate2">
<Border x:Name="container">
<StackPanel Orientation="Horizontal" >
</StackPanel>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding isTitulairePrincipal}" Value="True">
<Setter Property="Child" TargetName="container">
<Setter.Value>
<StackPanel Orientation="Vertical" >
</StackPanel>
</Setter.Value>
</Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</dxg:GridControl.Resources>
<dxg:GridControl.Columns>
<dxg:GridColumn FieldName="first_name"/>
<dxg:GridColumn FieldName="last_name"/>
</dxg:GridControl.Columns>
<dxg:GridControl.View>
<dxg:CardView x:Name="view" ShowGroupedColumns="True" CardTemplate="{DynamicResource cardTemplate2}" />
</dxg:GridControl.View>
</dxg:GridControl>
Any idea ?
Thanks
Jonathan
Define two separate data templates (call them CardTemplateV for the vertical one, and CardTemplateH for the horizontal one, for example), and set the CardTemplateSelector to a selector object that checks the discriminating field.
Example:
class MyTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var cardData = item as CardData;
if (cardData == null) return null;
var dataObject = cardData.DataContext as YourDataType;
if (dataObject == null) return null;
if (dataObject.isTitulairePrincipal)
return (DataTemplate)App.Current.FindResource("CardTemplateV");
else
return (DataTemplate)App.Current.FindResource("CardTemplateH");
}
}
In the XAML add the selector to the resource dictionary and reference it from the grid:
<Window.Resources>
<local:YourTemplateSelector x:Key="MyTemplateSelector"/>
</Window.Resources>
...
<dxg:CardView
x:Name="view"
ShowGroupedColumns="True"
CardTemplateSelector="{StaticResource MyTemplateSelector}"/>
...

How can I use a custom TabItem control when databinding a TabControl in WPF?

I have a custom control that is derived from TabItem, and I want to databind that custom TabItem to a stock TabControl. I would rather avoid creating a new TabControl just for this rare case.
This is what I have and I'm not having any luck getting the correct control to be loaded. In this case I want to use my ClosableTabItem control instead of the stock TabItem control.
<TabControl x:Name="tabCases" IsSynchronizedWithCurrentItem="True"
Controls:ClosableTabItem.TabClose="TabClosed" >
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type Controls:ClosableTabItem}" >
<TextBlock Text="{Binding Path=Id}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type Entities:Case}">
<CallLog:CaseReadOnlyDisplay DataContext="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
EDIT: This is what I ended up with, rather than trying to bind a custom control.
The "CloseCommand" im getting from a previous question.
<Style TargetType="{x:Type TabItem}" BasedOn="{StaticResource {x:Type TabItem}}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border
Name="Border"
Background="LightGray"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="25,0,0,0"
SnapsToDevicePixels="True">
<StackPanel Orientation="Horizontal">
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="20,1,5,1"/>
<Button
Command="{Binding Path=CloseCommand}"
Cursor="Hand"
DockPanel.Dock="Right"
Focusable="False"
Margin="1,1,5,1"
Background="Transparent"
BorderThickness="0">
<Image Source="/Russound.Windows;component/Resources/Delete.png" Height="10" />
</Button>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
<Setter TargetName="Border" Property="Background" Value="LightBlue" />
<Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
<Setter TargetName="Border" Property="BorderBrush" Value="DarkBlue" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
found a way,
derive a class from TabControl and override this function, in my case I want the items of the tab control (when bound) to be CloseableTabItems
public class CloseableTabControl : TabControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new CloseableTabItem();
}
}
HTH Someone
Sam
You don't want to set the DataType of the DataTemplate in this case. The value of the ItemTemplate property is used whenever a new item needs to be added, and in the case of a tab control it will be used to create a new TabItem. You should declare an instance of your class within the DataTemplate itself:
<TabControl x:Name="tabCases" IsSynchronizedWithCurrentItem="True" Controls:ClosableTabItem.TabClose="TabClosed">
<TabControl.ItemTemplate>
<DataTemplate>
<Controls:ClosableTabItem>
<TextBlock Text="{Binding Path=Id}" />
</Controls:ClosableTabItem>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type Entities:Case}">
<CallLog:CaseReadOnlyDisplay DataContext="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
This will cause a new ClosableTabItem to be created whenever a new tab is added to the TabControl.
Update; From your comment, it sounds like that the ItemTemplate controls what is created within the TabItem, rather than changing the TabItem itself. To do what you want to do, but for a TreeView, you would set the HeaderTemplate. Unfortunately, I don't see a HeaderTemplate property of TabControl.
I did some searching, and this tutorial modifies the contents of the tab headers by adding controls to TabItem.Header. Maybe you could create a Style for your TabItems that would add the close button that your class is currently adding?

Resources