Binding in CustomControl - wpf

I have created a custom control which is (among other things) able to display many expanders in an ItemsControl.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Style TargetType="{x:Type NxTabControl:NxCategoryControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type NxTabControl:NxCategoryControl}">
<ScrollViewer x:Name="contentScrollViewer" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<StackPanel HorizontalAlignment="Stretch" DockPanel.Dock="Bottom" VerticalAlignment="Stretch" Background="Transparent">
<ItemsControl ItemsSource="{TemplateBinding Content}">
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<Border>
<ItemsPresenter/>
</Border>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style> </ResourceDictionary>
The ItemsControl is binding to :
ItemsSource="{TemplateBinding Content}
Which is a DP in the class:
public class CategoryControl : ItemsControl
{
public ObservableCollection<Expander> Content
{
get { return (ObservableCollection<Expander>)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(ObservableCollection<Expander>), typeof(CategoryControl), new FrameworkPropertyMetadata(new ContentCollection(), FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsParentMeasure)); }
I am using my custom control in a user Control by setting the content Property and adding expanders to it:
<NxTabControl:NxCategoryControl x:Name="catControl" DockMode="Top" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderBrush="Green">
<TabControl:CategoryControl.Content>
<Expander Margin="0 3 0 3" IsExpanded="True" Header="Adresse" >
<ScrollViewer x:Name="ScrollViewerAdresse" HorizontalAlignment="Stretch" ockPanel.Dock="Left">
<Grid Width="{Binding ElementName=ScrollViewerAdresse, Path=ActualWidth, Converter={StaticResource HalfWidthConverter}}">
</Grid>
</ ScrollViewer>
</Expander>
<Expander>
</Expander>
<Expander Header={Binding ExpanderHeader}>
</Expander>
</TabControl:CategoryControl.Content>
My problem in this context is that I am not able to set this binding:
Grid Width="{Binding ElementName=ScrollViewerAdresse, Path=ActualWidth}"
And also no other binding that goes to an “ElementName”. I am e.g also binding to XamDataGrids.ActiveRecords by their name and other Elements. None of the bindings works.
Snoop tells me for the Width Property of the Grid:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=ScrollViewerAdresse'.
BindingExpression:Path=ActualWidth; DataItem=null; target element is 'Grid'; target property is 'Width' (type 'Double')
Other bindings I am using in my expanders, binding to the ViewModel of the usercontrol work though, e.g.:
Header={Binding ExpanderHeader}
Any help would be welcome! Thx!

Related

Apply style to child content in WPF XAML with a template

I'm using a ListViewto display a menu. I want to apply a style to all ListViewItem. So far I have succeeded in one part of the concern but I would like to improve to avoid code replication. Here where I am :
<Window.Resources>
<Style x:Key="_listViewItemStyle" TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border x:Name="Bd"
...
</Border>
<ControlTemplate.Triggers>
...
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
And then use the style like that :
<ListView x:Name="_listViewMenu"
Background="Transparent"
BorderBrush="Transparent"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListViewItem Style="{StaticResource _listViewItemStyle}">
<StackPanel Orientation="Horizontal"
Width="230"
Margin="10,0,0,0">
<Image Width="30"
Source="Images/Settings.png"
Stretch="Uniform"/>
<TextBlock Text="Paramètres"
Margin="25,0,0,0"
Style="{StaticResource _fontStyle}"/>
</StackPanel>
</ListViewItem>
</ListView>
As you can see there are some stuff that I imagine I can avoid to duplicate if I have 50 ListViewItems, let's say :
<StackPanel Orientation="Horizontal"
Width="230"
Margin="10,0,0,0">
or
<Image Width="30"
...
Stretch="Uniform"/>
etc...
How to include in the style _listViewItemStyle all required properties to format StackPanel, Image, textbox, etc... of the ListViewItem?
Thanks.
You can define a class which has necessary properties and use it as item type of collection for ItemsSource which may be in view (xaml or code behind). Such class would be as follows.
public class SourceItem
{
public string? ImagePath { get; init; }
public string? Text { get; init; }
}
Then, if you just want to show items, ItemsControl would be suffice. I don't know how you intend to use Triggers though.
<ItemsControl Background="Blue" HorizontalAlignment="Left">
<local:SourceItem ImagePath="Images/Settings.png" Text="Paramètres"/>
...
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border x:Name="Bd">
<StackPanel Orientation="Horizontal"
Width="230"
Margin="10,0,0,0">
<Image Width="30"
Source="{Binding ImagePath, Mode=OneTime}"
Stretch="Uniform"/>
<TextBlock Text="{Binding Text, Mode=OneTime}"
Margin="25,0,0,0"
Style="{StaticResource _fontStyle}"/>
</StackPanel>
</Border>
<DataTemplate.Triggers>
...
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In addition to the answer from #emoacht, you will need the following InputBindings:
<Border.InputBindings>
<MouseBinding Command="{Binding ItemCommand}" MouseAction="LeftClick"/>
</Border.InputBindings>

How to split a WPF TabControl's tabs on left and right?

I would like to have a TabControl with half of the tabs on the left, the other half on the right and the content presenter in the middle. See picture below.
I tried to edit the TabControl ControlTemplate and replace the TabPanel with a DockPanel with two StackPanels as follows:
<TabControl ItemsSource="{Binding Sequences}" SelectedIndex="0">
<TabControl.Style>
<Style TargetType="TabControl">
<Setter Property="OverridesDefaultStyle"
Value="True" />
<Setter Property="SnapsToDevicePixels"
Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabControl">
<DockPanel>
<StackPanel DockPanel.Dock="Left" IsItemsHost="True" Width="100"/>
<StackPanel DockPanel.Dock="Right" IsItemsHost="True" Width="100"/>
<ContentPresenter ContentSource="SelectedContent" Margin="100,5,5,5" />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Style>
<!-- ... -->
</TabControl>
But I get 6 tabs on each side with non working tabs on the left (there is no header and nothing displayed on the content presenter when I click on them).
How can I split TabControl's ItemsSource in my two StackPanels ?
Not exactly sure why this is working but it is. I stumbled across it by trying to solve this for you.
<TabControl>
<TabControl.Style>
<Style TargetType="TabControl" x:Name="myName">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<StackPanel Name="stk" Grid.Column="0" IsItemsHost="True" Width="100"/>
<ItemsControl Grid.Column="2" ItemsSource="{Binding ElementName=stk, Path=Children}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ContentPresenter Grid.Column="1" ContentSource="SelectedContent"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Style>
<TabItem Header="One">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="Black">1</TextBlock>
</TabItem>
<TabItem Header="two">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="Black">2</TextBlock>
</TabItem>
<TabItem Header="three">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="Black">3</TextBlock>
</TabItem>
<TabItem Header="four">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="Black">4</TextBlock>
</TabItem>
</TabControl>
If somebody could tell me how the framework decides which item to place where i'd be very courios!
Did not want to override my previous answer as it still a valid answer for those who dont want to bind the itemssource. I was able to make it look correct but the buttons dont work. Maybe you have an idea how to go on from here yourself:
<TabControl ItemsSource="{Binding Controls}">
<TabControl.Style>
<Style TargetType="TabControl" x:Name="myName" >
<Style.Resources>
<conv:ObservableCollectionSplitter x:Key="ObsColSplt"/>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<ItemsControl Grid.Column="2" ItemsSource="{TemplateBinding ItemsSource, Converter={StaticResource ObsColSplt}, ConverterParameter=even}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ItemsControl Grid.Column="0" ItemsSource="{TemplateBinding ItemsSource, Converter={StaticResource ObsColSplt}, ConverterParameter=uneven}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ContentPresenter Grid.Column="1" ContentSource="SelectedContent"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Style>
</TabControl>
and the converter (not my proudest bit of code):
public class ObservableCollectionSplitter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ObservableCollection<TabItem> partialCollection = null;
if (value != null)
{
partialCollection = new ObservableCollection<TabItem>();
ObservableCollection<TabItem> inputCollection = (value as ObservableCollection<TabItem>);
int a = 0;
if (parameter.ToString() == "even")
{
a = 1;
}
for (int i = a; i < inputCollection.Count; i = i + 2)
{
partialCollection.Add(inputCollection[i]);
}
}
return partialCollection;
}//END Convert
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}//END ConvertBack
}//END class BooleanInverter : IValueConverter

WPF ListBox WrapPanel: MouseOver and SelectedItem transparant

I have the following Listbox:
<ListBox Grid.Row="1" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ItemsSource="{Binding MyItems}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True">
</WrapPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
But when I do a mouseover or select an item, the item is "blue".
I want it to be transparant.
Also I want the item to be underlined if selected.
MyItem is an observableCollection of myItem:
public class MyItem
{
public string Key { get; set; }
public bool Selected{ get; set; }
}
You can for example simply setup the default style for the ListBoxItem at the top of your xaml file or ideally inside of your resources xaml file. You must override default control template in order to achive that. You don't have to setup triggers directly. Here's example. Have fun.
<Window x:Class="StackPoC.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:StackPoC"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter VerticalAlignment="{TemplateBinding VerticalAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<ListBox ItemsSource="{Binding Collection}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
EDIT
I haven't read the part about underline on Selected*. Well.. You already know that you had to create custom control template in order to get rid of the selection. I wont write the code for you but I will give you an idea on how to do that ;) Create Triggers inside of a node ControlTemplate.Triggers according to the IsSelected property and then use Setter in order to setup your desired appearance. Good luck.

WPF - Binding to current item from within group header style

I'm something of a WPF noob so please take it easy on me ;-)
I am trying to create a grouped DataGrid (WPF toolkit version).
I have successfully created the data source, the DataGrid itself, the required CollectionViewSource and the Style for the group header (which uses an expander).
I want to group by a property called 'Assign_To' and have the relevant value (the value that the grouped items share) show up in the header. However, I cannot work out how to bind to the current group/item in order to return its Assign_To property.
The closest I have got (shown below) is binding to the overall CollectionViewSource, which returns a fixed value for Assign_To. What would be the proper way to bind to the current item/group in order to return the correct value for 'Assign_To'?
Hope someone can help. Thanks!
Andy T.
Here's the source...
<Window DataContext="{Binding Source={StaticResource SampleDataSource}}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="DataGridTest.MainWindow"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480" mc:Ignorable="d">
<Window.Resources>
<CollectionViewSource x:Key="CVS" Source="{Binding MyData}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Assign_To"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style x:Key="GroupHeaderStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Assign To: "/>
<TextBlock Text="{Binding Source={StaticResource CVS}, Path=Assign_To}"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter/>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<dg:DataGrid
ItemsSource="{Binding Source={StaticResource CVS}}"
SelectionUnit="CellOrRowHeader"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeRows="False">
<dg:DataGrid.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupHeaderStyle}">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<dg:DataGridRowsPresenter/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</dg:DataGrid.GroupStyle>
</dg:DataGrid>
</Grid>
</Window>
Thanks for your reply. I really appreciate it and will check it out to see if it works.
Anyway, as it turns out, after some poking and prodding, I have worked it out using XAML only. What I had been missing was the fact that each item the group header is bound to is a GroupItem and that the default DataContext of a GroupItem is a CollectionViewGroup. In turn, a CollectionViewGroup has an Items property, which is a collection and I can therefore get the Assign_To value of the first item in the collection and use that in my header text. Like this:
<Style x:Key="GroupHeaderStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Assign To: "/>
<TextBlock Text="{Binding Items[0].Assign_To}"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter/>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
binding settings depend on the type of the Assign_To property. The simplest settings which could probably work for you would be:
<TextBlock Text="Assign To: "/>
<TextBlock Text="{Binding Name}"/>
pls, check if an example below would work for you; also this link WPF Toolkit DataGrid Part IV: TemplateColumns and Row Grouping might be helpful for you
code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var dataProvider = (CollectionViewSource)FindResource("CVS");
dataProvider.Source = Test.GetTests();
}
}
public class Test
{
public string Assign_To { get; set; }
public string Test0 { get; set; }
public int Test1 { get; set; }
public static List<Test> GetTests()
{
List<Test> tests = new List<Test>();
tests.Add(new Test { Assign_To = "a", Test0 = "aaaa", Test1 = 1 });
tests.Add(new Test { Assign_To = "a", Test0 = "bbbb", Test1 = 1 });
tests.Add(new Test { Assign_To = "b", Test0 = "cccc", Test1 = 2 });
return tests;
}
}
xaml:
<Window.Resources>
<CollectionViewSource x:Key="CVS" >
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Assign_To"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style x:Key="GroupHeaderStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Assign To: "/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<DataGrid
ItemsSource="{Binding Source={StaticResource CVS}}"
HorizontalScrollBarVisibility="Hidden" SelectionMode="Extended"
AutoGenerateColumns="False"
Name="dataGrid1">
<DataGrid.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupHeaderStyle}">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<DataGridRowsPresenter/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Test0" Binding="{Binding Path=Test0}" />
<DataGridTextColumn Header="Test1" Binding="{Binding Path=Test1}" />
</DataGrid.Columns>
</DataGrid>
</Grid>

Data Template not injected in ItemContainer

I have an ItemsControl class that overrides the following methods:
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TilePanelItem;
}
protected override DependencyObject GetContainerForItemOverride()
{
return new TilePanelItem();
}
I provided the template for TilePanelItem which is the container of ItemsControl:
<ControlTemplate x:Key="tileItemTemplate" TargetType="my:TilePanelItem">
<Grid Width="200" Height="100">
<Border BorderBrush="Black" BorderThickness="2">
<ContentPresenter RenderTransformOrigin=".5,.5" />
</Border>
</Grid>
</ControlTemplate>
And the Style for it:
<Style TargetType="my:TilePanelItem">
<Setter Property="Template" Value="{StaticResource tileItemTemplate}" />
</Style>
And finally the instance of my ItemsControl with the DataTemplate:
<my:TileItemsControl x:Name="tileControl" ItemsSource="{Binding}" >
<my:TileItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</my:TileItemsControl.ItemsPanel>
<my:TileItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="Blue">
<TextBlock Text="here I am" />
</Border>
</DataTemplate>
</my:TileItemsControl.ItemTemplate>
</my:TileItemsControl>
The DataTemplate that I defined is not injected in ItemContainer. When I run the app, it shows the borders as I declared in ItemContainter template but I don't see the DataTemplate.
What am I missing?
Thanks a million
You need to bring the ItemsTemplate in to become the ContentPresenter's ContentTemplate:
<ControlTemplate x:Key="tileItemTemplate" TargetType="my:TilePanelItem">
<Grid Width="200" Height="100">
<Border BorderBrush="Black" BorderThickness="2">
<ContentPresenter RenderTransformOrigin=".5,.5"
ContentTemplate="{Binding ItemsTemplate, RelativeSource={RelativeSource FindAncestor,my:TileItemsControl,1}}" />
</Border>
</Grid>
</ControlTemplate>

Resources