I'm new in WPF and i'm executing some simulations to try understanding the binding "things" (Like {Binding}, {TemplateBinding}...)
Ok, so I make this sample here to test TemplateBinding and works well:
<Button Width="100" Height="100">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Image Source="image.png" Width="{TemplateBinding Width}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
Now I tried the same but with others properties:
<Button Width="100" Height="100" Content="image.png">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Image Source="{TemplateBinding Content}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
In the first case, the binding occurs perfectly. But in the second nothing happens.
What I'm missing here? There is some properties that can be bind others can't?
I'm really lost here, can someone explain to me why one works and the other not?
TemplateBinding is evaluated at compile time and does not provide automatic value type conversion (in this case string to ImageSource). Just use TemplatedParent relative source binding instead
<Image Source="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" />
Another thing, not directly related to this problem, is that if you want to use TemplateBinding for other properties of the same type make sure you set TargetType accordingly. TemplateBinding is evaluated against ControlTemplate type and default TargetType is System.Windows.Controls.Control
<ControlTemplate TargetType="{x:Type Button}">
Your bindings should have matching types. Button.Content is an object while Image.Source expects an Uri. Instead of Content use Tag and convert it via a converter:
<Button Width="100" Height="100" Tag="image.png">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Image Source="{TemplateBinding Tag, Converter={StaticResource ObjectToUriConverter}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
Converter
public class ObjectToUriConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return null;
return new Uri(value.ToString());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I think you might get the Content is not recognised or is not accssible error , rather than you can use <Image Source="{Binding Content,ElementName=btn}" /> where btn is the Button Name.
Related
I have tabcontrol which uses an observable collection of tabitems as itemsource. I dont want the close button for the first tab alone. So i added the style trigger
<Style TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid Background="White">
<Border Name="Border" BorderBrush="#1b9ed2" Margin="6,0,12,0" Background="White">
<ContentPresenter Height="30" x:Name="ContentSite" ContentSource="Header" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="5,15,5,-5">
</ContentPresenter>
</Border>
<Button Background="Transparent" BorderBrush="Transparent" Name="TabCloseButton" Click="TabCloseButton_Click" HorizontalAlignment="Right" VerticalAlignment="Bottom" ToolTip="Close" HorizontalContentAlignment="Right" Padding="0">
<materialDesign:PackIcon Kind="Close" Foreground="Gray" HorizontalAlignment="Right"/>
</Button>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="TabIndex" Value="0">
<Setter TargetName="TabCloseButton" Property="Visibility" Value="Collapsed"/>
</Trigger>
......
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="FontSize" Value="12"/>
</Style>
</TabControl.Resources>
It works if i just added the items in xaml
<TabControl Name ="ConnectionsTab">
<TabItem Header="...." TabIndex="0"></TabItem>
<TabItem Header="...." TabIndex="1"></TabItem>
</TabControl>
But it doesn't works when I make the itemsource to the tabcontrol
private ObservableCollection<TabItem> tabItems = new ObservableCollection<TabItem>();
inside constructor
tabItems.Add(new TabItem { Header = "Connections", Content = new ResourceAccountDisplayUserControl(response), TabIndex = 0 });
ConnectionsTab.ItemsSource = tabItems;
I don't know why it does not work. Any idea or code to make it work will be helpful.
First of all, TabIndex is not the index if your TabItem inside the TabControl. So, you need a different way to determine the actual tab position.
If your data source implements IList, you could for example write a MultiValueConverter that determines the position of an item within a collection:
public class MyTabItemIndexConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// TODO: add some validation to avoid unsupported values
var c = (IList)values[1];
return c.IndexOf(values[0]);
}
public object[] ConvertBack(object values, Type[] targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Side Note: there are probably other ways to determine the index like Determining index of a tabitem.
Then you can use this converter, to store the actual item index somewhere (or use it directly). In this example, I set the TabItem.Tag property to the item index:
<Window.Resources>
<local:MyTabItemIndexConverter x:Key="tabItemIndexConverter"/>
</Window.Resources>
[...]
<TabControl Name="tabControl1" ItemsSource="{Binding}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Tag">
<Setter.Value>
<MultiBinding Converter="{StaticResource tabItemIndexConverter}">
<Binding />
<Binding ElementName="tabControl1" Path="ItemsSource" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
[...]
</TabControl>
Now with TabItem.Tag you have a property that you can actually use in order to implement your trigger logic.
I have a problem with a binding, I want to change an icon when the column has at least one element filtered. But it is not working.
This is my converter:
public class FilteredToIconConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int index = ((DataGridColumnHeader)((MahApps.Metro.IconPacks.PackIconMaterial)value).TemplatedParent).Column.DisplayIndex;
return !((AdvancedDataGrid)parameter).FilterLists[index].Any(item => item.NotFiltered);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
A filter for each column is generated with a contentTemplate, and I have 2 problems, If I set this style in controlTemplate.Resources the dataGrid reference is not found.
If I set the style in the DataGrid.Resources then I get
InvalidCastException: Can not convert an object from type 'System.Windows.Controls.DataGrid' to type 'MahApps.Metro.IconPacks.PackIconMaterial'.
This is the style:
<Style TargetType="iconPacks:PackIconMaterial">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource FilteredToIcon}, ConverterParameter={x:Reference dataGrid}}" Value="True">
<Setter Property="Kind" Value="FilterMenu"/>
</DataTrigger>
</Style.Triggers>
</Style>
And this is a summary of the whole XAML of my custom AdvancedDataGrid:
<DataGrid x:Class="ZOT.GUI.Items.AdvancedDataGrid"
x:Name="dataGrid"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:local="clr-namespace:ZOT.GUI.Items">
<DataGrid.Resources>
<local:FilteredToIconConverter x:Key="FilteredToIcon" />
<!-- The style can be here-->
</DataGrid.Resources>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<ControlTemplate.Resources>
<!-- The style can be here as well, whatever that works-->
</ControlTemplate.Resources>
<Grid>
<ContentPresenter/>
<ToggleButton x:Name="Y">
<!-- This is the Icon I want to change -->
<iconPacks:PackIconMaterial Kind="Filter"/>
</ToggleButton>
<Popup x:Name="pop" Width="auto" IsOpen="{Binding IsChecked, ElementName=Y,Mode=TwoWay}">
<!-- .... -->
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>
If you have any clue, tell me please.
Thank you.
If you want to bind to the PackIconMaterial itself, you should set the RelativeSource property of the binding to RelativeSource.Self:
<Style TargetType="iconPacks:PackIconMaterial">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource FilteredToIcon}, ConverterParameter={x:Reference dataGrid},
RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Kind" Value="FilterMenu"/>
</DataTrigger>
</Style.Triggers>
</Style>
That's the only way you will be able to cast value to MahApps.Metro.IconPacks.PackIconMaterial in the converter.
There are probably better ways of solving whatever you're trying to do though.
I have TabControl which is used as a main element of my app:
Every tab content would have a title (duplicate to the menu name), separater and content. My XAML code which must correspond to this view should be like this:
<TabControl Style="{StaticResource MyCustomStyle}">
<TabItem Header="Menu 1">
<TabItem.Content>
...
</TabItem.Content>
</TabItem>
<TabItem Header="Menu 2">
<TabItem.Content>
<TextBlock>Content</TextBlock>
</TabItem.Content>
</TabItem>
...
</TabControl>
To avoid duplicating parts of the code, I decided to setup view in custom style.
MyCustomStyle for this:
<Style x:Key="MyCustomStyle" TargetType="{x:Type TabControl}">
<Style.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Padding" Value="10"/>
<Setter Property="Width" Value="120"/>
<Setter Property="Content">
<Setter.Value>
<StackPanel Margin="10">
<Label FontSize="20" Content="..."/>
<Separator/>
<ContentPresenter ContentSource="..."/>
</StackPanel>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="TabStripPlacement" Value="Left"/>
</Style>
The only problem occurs with the modification of content. Label don't want to bind value to the TabItem's header. The same story for ContentPresenter
I tried to use RelativeSource for this, but this is not working:
<Label FontSize="20" Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Header}"/>
If I understand correctly, you're very close -- you just want to give it a ContentTemplate instead of setting the Content in the style:
<Style x:Key="MyCustomStyle" TargetType="{x:Type TabControl}">
<Style.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Padding" Value="10"/>
<Setter Property="Width" Value="120"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Margin="10">
<Label FontSize="20" Content="..."/>
<Separator/>
<ContentControl
Content="{Binding}"
/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="TabStripPlacement" Value="Left"/>
</Style>
Now, for the Label content. You can't get that via RelativeSource AncestorType because TabItem isn't in the visual tree: If you write a VisualTreeHelper.GetParent() loop, you find that the parent chain hits a bunch of random stuff like Grids and whatnot, then suddenly it's on TabControl.
So what we do instead, is we write a multi-value converter. We give it the DataContext for the DataTemplate -- that's the Content for the TabItem being ContentTemplated -- and the TabControl. Then we look through the TabControl's Items to find the TabItem that has the same Content.
I tried this as a regular valueconverter, just passing in {RelativeSource Self} from the Label, walking the visual tree inside the converter to find the TabControl, and using the Label's DataContext inside the converter to identify the TabItem I wanted. That didn't work because of (I think) virtualization: The DataTemplate is instantiated once and those controls are reused. Since the value of {RelativeSource Self} is the same each time, and I didn't tell the Binding anything about DataContext, the value converter was only invoked for the first TabItem that was ever selected. The multi-value converter fixes that problem by explicitly binding to ., the DataContext.
This breaks if you use ItemsSource to populate the TabControl. In fact, it breaks if you populate the TabControl with anything but TabItems just like you did. But then, you couldn't be setting their Header properties otherwise, and if you were using ItemsSource, you'd have all kinds of good things to bind to and you wouldn't be contemplating nutty expedients like this one.
TabItemHeaderConverter.cs
public class TabItemHeaderConverter : IMultiValueConverter
{
// This is pretty awful, but nobody promised life would be perfect.
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var tc = values[0] as TabControl;
var tabItemContent = values[1];
var tabItem = tc.Items.Cast<TabItem>().FirstOrDefault(ti => ti.Content == tabItemContent);
if (null != tabItem)
{
return tabItem.Header;
}
return "Unknown";
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And the new XAML:
<Style x:Key="MyCustomStyle" TargetType="{x:Type TabControl}">
<Style.Resources>
<local:TabItemHeaderConverter x:Key="TabItemHeaderConverter" />
<Style TargetType="{x:Type TabItem}">
<Setter Property="Padding" Value="10"/>
<Setter Property="Width" Value="120"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Margin="10">
<Label
FontSize="20"
>
<Label.Content>
<MultiBinding Converter="{StaticResource TabItemHeaderConverter}">
<Binding RelativeSource="{RelativeSource AncestorType=TabControl}" />
<Binding Path="." />
</MultiBinding>
</Label.Content>
</Label>
<Separator />
<ContentControl
Content="{Binding}"
/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="TabStripPlacement" Value="Left"/>
</Style>
I have the following ControlTemplate for the Validation of a ComboBox
<ControlTemplate x:Key="ComboBoxHighlightTemplate" TargetType="Control">
<Grid ClipToBounds="False">
<Border BorderBrush="{StaticResource fokusBrush}" BorderThickness="2.5" Margin="-1" Visibility="{Binding ElementName=adornedElementHighligh,
Path=AdornedElement.(Validation.Errors)[0].ErrorContent, Converter={StaticResource inverseErrorContentVisibilityConverter}}">
<AdornedElementPlaceholder Name="adornedElementHighligh"/>
</Border>
<Border BorderBrush="Red" BorderThickness="1" Margin="-1" Visibility="{Binding ElementName=adornedElement,
Path=AdornedElement.(Validation.Errors)[0].ErrorContent, Converter={StaticResource errorContentToErrorVisibilityConverter}}">
<AdornedElementPlaceholder Name="adornedElement" />
</Border>
<Image HorizontalAlignment="Right" VerticalAlignment="Top" Visibility="{Binding ElementName=adornedElement,
Path=AdornedElement.(Validation.Errors)[0].ErrorContent, Converter={StaticResource errorContentToErrorVisibilityConverter}}"
Width="16" Height="16" Margin="0,-9,-9,0" Source="{x:Static helper:ImageHelper.ErrorImage}"
ToolTip="{Binding ElementName=adornedElement, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
</Grid>
</ControlTemplate>
Depending on the ErrorContent I decide which Border I draw around the ComboBox.
The Converter ErrorContentToErrorVisibilityConverter looks like
internal class ErrorContentToErrorVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string)
{
if (((string) value).EndsWith("not an error"))
return Visibility.Hidden;
}
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Everything just works fine. But if I produce an error in my ComboBox so that the ControlTemplate for Errors is shows, the bounds of the drawn borders are not at the "real" borders of the ComboBox.
The usage of the ControlTemplate looks like:
here is a sample template, I did try to simplify it for you
<ControlTemplate x:Key="ComboBoxHighlightTemplate"
TargetType="Control">
<Grid ClipToBounds="False">
<AdornedElementPlaceholder Name="adornedElement" />
<Border BorderBrush="Red"
BorderThickness="1"
x:Name="errorBorder" />
<Image HorizontalAlignment="Right"
VerticalAlignment="Top"
x:Name="image"
Width="16"
Height="16"
Margin="0,-9,-9,0"
Source="{x:Static helper:ImageHelper.ErrorImage}"
ToolTip="{Binding ElementName=adornedElement, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding AdornedElement.(Validation.Errors)[0].ErrorContent, ElementName=adornedElement,Converter={StaticResource errorContentToErrorVisibilityConverter}}"
Value="Hidden">
<Setter Property="BorderBrush"
TargetName="errorBorder"
Value="{StaticResource fokusBrush}" />
<Setter Property="BorderThickness"
TargetName="errorBorder"
Value="2.5" />
<Setter Property="Visibility"
TargetName="image"
Value="Collapsed" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
try it and let me know how close is this to your results, we may also simplify the trigger a bit further, I did this on assumptions.
I have Created and Implemented WPf UserContol like Window8 PasswordBox. When I Change the Font Both Textbox and Inner Button has Changed. Unfortunately Button Font size not suitable when textbox font size has perfect. (See the Image - Second Button has perfect button font size, but textbox has not. And third Button has not perfect, but textbox has perfect font size)
How Can I make to set two Controls font size while Implementation? Like the Property Button_fontSize and textbox_fontSize.
My Usercontol XAML Code:
<Grid.Resources>
<Style x:Key="ButtonWithoutHover" TargetType="Button">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Name="border"
BorderThickness="3"
BorderBrush="White"
Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="BorderBrush" Value="Black" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<Border BorderBrush="White" BorderThickness="2" >
<DockPanel Canvas.Right="2" Canvas.Top="2">
<Button Style="{StaticResource ButtonWithoutHover}" BorderThickness="3" BorderBrush="White" DockPanel.Dock="Right" Click="onButtonClick" >
<Button.Content>
<Label Content="->" Foreground="White" />
</Button.Content>
</Button>
<TextBox BorderThickness="0" Name="txtNumber" DockPanel.Dock="Left" >
</TextBox>
</DockPanel>
</Border>
Implementation XMAL Code:
<NumText:UserControl1 Click="UserControl1_Click" FontSize="9" Background="Red" Foreground="Yellow" Margin="160,46,206,229" />
<NumText:UserControl1 Click="UserControl1_Click" FontSize="20" Background="Red" Foreground="Yellow" Margin="121,104,173,145" />
<NumText:UserControl1 Background="Red" FontSize="36" Foreground="Yellow" Margin="121,180,173,69" />
One thing I've done in my work is create a Double resource to set the FontSize property. I believe it will solve your problem:
First, declare the namespace in your XAML file:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Then create two distinct Double "variables" with x:Key:
<sys:Double x:Key="SmallFont">10</sys:Double>
<sys:Double x:Key="LargeFont">35</sys:Double>
At last set your Control's FontSize property:
FontSize="{DynamicResource SmallFont}"
or
FontSize="{DynamicResource LargeFont}"
If I understood you correctly, this is how I've done! Hope it Helps.
You have overcomplicated the situation unnecessarily... you don't need different sized fonts, or DockPanels, or Borders. Instead of all this, just let the content size the UserControl, not the other way around. Try this simplified example (to use in the Usercontrol):
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="jagadees" BorderThickness="0"
VerticalAlignment="Center" />
<Button Grid.Column="1" Style="{StaticResource ButtonWithoutHover}"
Background="Red" Foreground="White" Content="->" VerticalAlignment="Center" />
</Grid>
UPDATE >>>
My whole point was that if you restructured your XAML then you would see that you didn't need to use different font sizes. However, if you are resolute that you do, then just implement a simple Converter class to change the size for one of the elements:
public class DoubleToLargerDoubleConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || value.GetType() != typeof(double)) return false;
double amountToEnlargeBy = 0;
if (parameter == null || double.TryParse(parameter.ToString(), out amountToEnlargeBy)) return false;
double doubleValue = (double)value;
return doubleValue + amountToEnlargeBy;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
As you never actually said which text you wanted bigger, I'm just guessing that the Button text should be bigger. In this case, you'd use it like this:
<Label Content="->" Foreground="White" FontSize="{Binding FontSize, RelativeSource={
RelativeSource AncestorType={x:Type Button}}, Converter={StaticResource
DoubleToLargerDoubleConverter}, ConverterParameter=5.0}" />
I'm going to assume that you know how to use a Converter, so let me know if you don't. Just set the amount that you want the font size to be enlarged by in the
Binding.ConverterParameter property.