How to use customTabItem in TabControls in WPF - c#

I am using TabControl with ItemSource as List. Based on the List, TabItems are getting generated.
<TabControl Width="Auto" VerticalAlignment="Top" ItemsSource="{Binding MyTabItems, Mode=TwoWay}" HorizontalAlignment="Stretch">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}" BasedOn="{StaticResource TabItemStyle}" >
<Setter Property="FontSize" Value="10pt"/>
</Style>
</TabControl.Resources>
</TabControl>
I have created my own class MyTabItem inheriting from TabItem. I would like to use MyTabItem in this TabControl.
Can someone suggest a way to do it?

Use DataTemplate
<TabControl x:Name="tabCases" IsSynchronizedWithCurrentItem="True" Controls:ClosableTabItem.TabClose="TabClosed">
<TabControl.ItemTemplate>
<DataTemplate>
<Controls:MyTabItem >
<TextBlock Text="{Binding Path=Id}" />
</Controls:MyTabItem >
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>

If you want to change the type of the containers from TabItem to your custom type, you should create a custom TabControl and override its GetContainerForItemOverride() method:
public class MyTabControl : TabControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new MyTabItem();
}
}
Of course you must also use this custom type instead of the built-in one in your XAML:
<local:MyTabControl Width="Auto" VerticalAlignment="Top" ItemsSource="{Binding MyTabItems, Mode=TwoWay}" HorizontalAlignment="Stretch">
<TabControl.Resources>
<Style TargetType="{x:Type local:MyTabItem}" BasedOn="{StaticResource TabItemStyle}" >
<Setter Property="FontSize" Value="10pt"/>
</Style>
</TabControl.Resources>
</local:MyTabControl>

Related

Binding in resources [duplicate]

I've got a DataGrid with a row that has an image. This image is bound with a trigger to a certain state. When the state changes I want to change the image.
The template itself is set on the HeaderStyle of a DataGridTemplateColumn. This template has some bindings. The first binding Day shows what day it is and the State changes the image with a trigger.
These properties are set in a ViewModel.
Properties:
public class HeaderItem
{
public string Day { get; set; }
public ValidationStatus State { get; set; }
}
this.HeaderItems = new ObservableCollection<HeaderItem>();
for (int i = 1; i < 15; i++)
{
this.HeaderItems.Add(new HeaderItem()
{
Day = i.ToString(),
State = ValidationStatus.Nieuw,
});
}
Datagrid:
<DataGrid x:Name="PersoneelsPrestatiesDataGrid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
AutoGenerateColumns="False" SelectionMode="Single" ItemsSource="{Binding CaregiverPerformances}" FrozenColumnCount="1" >
<DataGridTemplateColumn HeaderStyle="{StaticResource headerCenterAlignment}" Header="{Binding HeaderItems[1]}" Width="50">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{ Binding Performances[1].Duration,Converter={StaticResource timeSpanConverter},Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock TextAlignment="Center" Text="{ Binding Performances[1].Duration,Converter={StaticResource timeSpanConverter}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid>
Datagrid HeaderStyleTemplate:
<Style x:Key="headerCenterAlignment" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Day}" />
<Image x:Name="imageValidation" Grid.Row="1" Width="16" Height="16" Source="{StaticResource imgBevestigd}" />
</Grid>
<ControlTemplate.Triggers>
<MultiDataTrigger >
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding State}" Value="Nieuw"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="imageValidation" Property="Source" Value="{StaticResource imgGeenStatus}"/>
</MultiDataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now when I startup the project the images doesn't show and I get this error:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=HeaderItems[0]; DataItem=null; target element is 'DataGridTemplateColumn' (HashCode=26950454); target property is 'Header' (type 'Object')
Why is this error showing?
Sadly any DataGridColumn hosted under DataGrid.Columns is not part of Visual tree and therefore not connected to the data context of the datagrid. So bindings do not work with their properties such as Visibility or Header etc (although these properties are valid dependency properties!).
Now you may wonder how is that possible? Isn't their Binding property supposed to be bound to the data context? Well it simply is a hack. The binding does not really work. It is actually the datagrid cells that copy / clone this binding object and use it for displaying their own contents!
So now back to solving your issue, I assume that HeaderItems is a property of the object that is set as the DataContext of your parent View. We can connect the DataContext of the view to any DataGridColumn via something we call a ProxyElement.
The example below illustrates how to connect a logical child such as ContextMenu or DataGridColumn to the parent View's DataContext
<Window x:Class="WpfApplicationMultiThreading.Window5"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vb="http://schemas.microsoft.com/wpf/2008/toolkit"
Title="Window5" Height="300" Width="300" >
<Grid x:Name="MyGrid">
<Grid.Resources>
<FrameworkElement x:Key="ProxyElement" DataContext="{Binding}"/>
</Grid.Resources>
<Grid.DataContext>
<TextBlock Text="Text Column Header" Tag="Tag Columne Header"/>
</Grid.DataContext>
<ContentControl Visibility="Collapsed"
Content="{StaticResource ProxyElement}"/>
<vb:DataGrid AutoGenerateColumns="False" x:Name="MyDataGrid">
<vb:DataGrid.ItemsSource>
<x:Array Type="{x:Type TextBlock}">
<TextBlock Text="1" Tag="1.1"/>
<TextBlock Text="2" Tag="1.2"/>
<TextBlock Text="3" Tag="2.1"/>
<TextBlock Text="4" Tag="2.2"/>
</x:Array>
</vb:DataGrid.ItemsSource>
<vb:DataGrid.Columns>
<vb:DataGridTextColumn
Header="{Binding DataContext.Text,
Source={StaticResource ProxyElement}}"
Binding="{Binding Text}"/>
<vb:DataGridTextColumn
Header="{Binding DataContext.Tag,
Source={StaticResource ProxyElement}}"
Binding="{Binding Tag}"/>
</vb:DataGrid.Columns>
</vb:DataGrid>
</Grid>
</Window>
The view above encountered the same binding error that you have found if I did not have implemented the ProxyElement hack. The ProxyElement is any FrameworkElement that steals the DataContext from the main View and offers it to the logical child such as ContextMenu or DataGridColumn. For that it must be hosted as a Content into an invisible ContentControl which is under the same View.
I hope this guides you in correct direction.
A slightly shorter alternative to using a StaticResource as in the accepted answer is x:Reference:
<StackPanel>
<!--Set the DataContext here if you do not want to inherit the parent one-->
<FrameworkElement x:Name="ProxyElement" Visibility="Collapsed"/>
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn
Header="{Binding DataContext.Whatever, Source={x:Reference ProxyElement}}"
Binding="{Binding ...}" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
The main advantage of this is: if you already have an element which is not a DataGrid's ancestor (i.e. not the StackPanel in the example above), you can just give it a name and use it as the x:Reference instead, hence not needing to define any dummy FrameworkElement at all.
If you try referencing an ancestor, you will get a XamlParseException at run-time due to a cyclical dependency.
The way without a proxy is to set bindings in the constructor:
var i = 0;
var converter = new BooleanToVisibilityConverter();
foreach(var column in DataGrid.Columns)
{
BindingOperations.SetBinding(column, DataGridColumn.VisibilityProperty, new Binding($"Columns[{i++}].IsSelected")
{
Source = ViewModel,
Converter = converter,
});
}
The Proxy Element didn't work for me, for a tooltip. For an infragistics DataGrid I did this, you might change it easily to your kind of grid:
<igDP:ImageField Label="_Invited" Name="Invited">
<igDP:Field.Settings>
<igDP:FieldSettings>
<igDP:FieldSettings.CellValuePresenterStyle>
<Style TargetType="{x:Type igDP:CellValuePresenter}">
<Setter Property="ToolTip">
<Setter.Value>
<Label Content="{Binding DataItem.InvitationSent, Converter={StaticResource dateTimeConverter}}"/>
</Setter.Value>
</Setter>
</Style>
</igDP:FieldSettings.CellValuePresenterStyle>
</igDP:FieldSettings>
</igDP:Field.Settings>
</igDP:ImageField>

Set Header of TabItem

I have a TabControl which is bound to a List of UserControls (MyControls)
<TabControl Background="{x:Null}" x:Name="MyView" ItemsSource="{Binding MyControls}" >
I want to bind the header of each tab item to a property(Title) in each UserControl. Which I did as below
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Title}"/>
</Style>
</TabControl.ItemContainerStyle>
However since I override the ItemContainerStyle I lost all the default style for the application. My tab header looks different from other tab headers in the application
Is there any way to just bind to the Title without changing any style?
Define an ItemTemplate:
<TabControl Background="{x:Null}" x:Name="MyView" ItemsSource="{Binding MyControls}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
MyControls shouldn't return an IEnumerable<UserControl> though. It should return an IEnumerable<YourObject> where YourObject is a POCO class with a Title property along with any other properties. You should then use DataTemplates to define the appearance of a YourObject.

Binding to a dependency property of a behavior inside a child control

I have the following user control with a custom dependency property
ThumbnailListView UserControl
<ListView
ItemsSource="{Binding}"
BorderThickness="0,0,0,0"
HorizontalAlignment="Center"
Background="White"
SelectionChanged="ListView_SelectionChanged"
AllowDrop="True">
<i:Interaction.Behaviors>
<behaviors:DragDropBehavior OnDragDrop="{Binding Path=ItemDragDrop}"></behaviors:DragDropBehavior>
</i:Interaction.Behaviors>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource IsLastListItem}}" Value="False">
<Setter Property="Margin" Value="0,0,0,20"></Setter>
</DataTrigger>
</Style.Triggers>
<Setter Property="Background" Value="Gray"></Setter>
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Thumbnail, Converter={StaticResource ImageConverter}}"></Image>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Dependency Property ItemDragDrop of ThumbnailListView
public static ICommand GetItemDragDrop(DependencyObject obj)
{
return (ICommand)obj.GetValue(ItemDragDropProperty);
}
public static void SetItemDragDrop(DependencyObject obj, ICommand value)
{
obj.SetValue(ItemDragDropProperty, value);
}
public static DependencyProperty ItemDragDropProperty = DependencyProperty.RegisterAttached("ItemDragDrop",
typeof(ICommand), typeof(ThumbnailListView));
public ICommand ItemDragDrop
{
get
{
return (ICommand)GetValue(ItemDragDropProperty);
}
set
{
SetValue(ItemDragDropProperty, value);
}
}
NewScansView UserControl
<DockPanel Dock="Top">
<ListView ItemsSource="{Binding Scans}" Width="500">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Caption}" Margin="5,0,0,0"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
<Views:ThumbnailListView DataContext="{Binding SelectedItem.Pages}" ItemDragDrop="{Binding SelectedItem.DragDropCommand}" VerticalAlignment="Stretch" Width="200" />
<Views:PageListView DataContext="{Binding SelectedItem.Pages}" VerticalAlignment="Stretch" />
</DockPanel>
The NewScansView xaml contains a ThumbnailListView control and has bound the dependency property ItemDragDrop of the ThumbnailListView to a command in the class of SelectedItem.
Inside the ThumbnailListView user control I have a behavior DragDropBehavior which has a dependency property OnDragDrop.
I am trying to bind OnDragDrop to ItemDragDrop so that when a drag drop operation completes the command in the SelectedItem class is executed.
The problem is that it doesn't seem to be able to find either the ItemDragDrop property on the ThumbnailListView or the DragDropCommand of the selected item class.
I'm wondering what i'm doing wrong and how I can set it up?
ThumbnailListView.DataContext is set to SelectedItem.Pages, and I highly doubt that SelectedItem.Pages has a property called SelectedItem.DragDropCommand.
Change the Source of your ItemDragDrop binding to specify it uses something other than the ThumnailListView.DataContext for the source of that binding.
<DockPanel x:Name="MyDockPanel" Dock="Top">
...
<Views:ThumbnailListView DataContext="{Binding SelectedItem.Pages}"
ItemDragDrop="{Binding SelectedItem.DragDropCommand, ElementName=MyDockPanel}" ... />
...
</DockPanel>
You probably also have to do the same for your behavior binding - change the source of it so it points to the ThumbnailListView instead of to the ThumbnailListView.DataContext
<i:Interaction.Behaviors>
<behaviors:DragDropBehavior OnDragDrop="{Binding Path=ItemDragDrop, RelativeSource={RelativeSource AncestorType={x:Type local:ThumbnailListView}}}" />
</i:Interaction.Behaviors>
Or better yet, either make a DependencyProperty for Pages so you don't rely on a specific DataContext object type
<Views:ThumbnailListView PagesDependencyProperty="{Binding SelectedItem.Pages}" ItemDragDrop="{Binding SelectedItem.DragDropCommand}" .. />
Or edit your control so it assumes a specific type is being used for the DataContext, and use an implicit DataTemplate to always draw that type of object using this control (far more common for me) :
<ListView ItemsSource="{Binding Pages}" ...>
<i:Interaction.Behaviors>
<behaviors:DragDropBehavior OnDragDrop="{Binding DragDropCommand}" />
</i:Interaction.Behaviors>
...
</ListView>
Implicit DataTemplate:
<DataTemplate DataType="{x:Type local:WhateverSelectedItemDataTypeIs}}">
<!-- DataContext will automatically set to WhateverSelectedItemDataTypeIs -->
<Views:ThumbnailListView />
</DataTemplate>

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}" />

Binding the IsSelectedProperty of a ListBox in WPF is not working. Need Help

I am trying to do something that should be brain-dead simple, however, I cannot get it to work. I am displaying a list of items in a listbox. I have added check boxes to the list box so that the user can select multiple items. However, even though the object in the list being bound to the ListBox has an "IsSelected" property, it is not being bound. I could use some help as this is driving me nuts.
<Style x:Key="CheckBoxListStyle" TargetType="{x:Type ListBox}">
<Setter Property="SelectionMode" Value="Multiple"></Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Margin" Value="2"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<CheckBox Focusable="False"
IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}">
<ContentPresenter></ContentPresenter>
</CheckBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
<ListBox
Style="{StaticResource CheckBoxListStyle}"
IsEnabled="{Binding Path=SpecificClients.Value, Mode=OneWay}"
ItemsSource="{Binding Path=SelectedClients}"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
ScrollViewer.VerticalScrollBarVisibility="Auto"
MaxHeight="95">
</ListBox>
In the View Model I have the following:
public IEnumerable<SelectedClientVM> SelectedClients
....
public class SelectedClientVM
{
public bool IsSelected { get; set; }
public Client Client { get; set; }
public override string ToString()
{
return Client.SearchText;
}
}
I think what you want can be better achieved by defining a DataTemplate to be used for each item in the ListBox. A DataTemplate specifies how you want an individual piece of data (a Client in your case) rendered in the ListBox.
Here's my XAML for a simple DataTemplate.
<DataTemplate x:Key="clientTemplate" DataType="{x:Type local:Client}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding IsSelected}" />
<TextBlock Grid.Column="1" Text="{Binding Name}" Margin="5,0,0,0" />
</Grid>
</DataTemplate>
Here's how I referenced it in the ListBox declaration:
<ListBox ItemsSource="{Binding SelectedClients}"
VirtualizingStackPanel.IsVirtualizing="True"
ItemTemplate="{StaticResource clientTemplate}" />
Secondly, to Grant's answer, you'll want to be sure that your Client class implements INotifyPropertyChanged. Plus, you'll want to expose your list of Clients using a collection that supports change notifications. I usually use ObservableCollection<T>.
This may not be the only issue, but if you want the view to update based on your ViewModel you'll have to implement INotifyPropertyChanged (or something that does a similar job) on your IsSelected property.

Categories

Resources