I am defining a custom control in WPF which serves as the base class for multiple dialog windows which all leverage shared services (positioning, Ok and Cancel buttons). The custom control is defined as follows:
public class ESDialogControl : Window
{
static ESDialogControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ESDialogControl), new FrameworkPropertyMetadata(typeof(ESDialogControl)));
}
internal ESDialogControl() { }
public ESDialogControl(string title)
{
Title = title;
this.KeyDown += new KeyEventHandler(ESDialogControl_KeyDown);
}
void ESDialogControl_KeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Escape: cancelButton_Click(null, null); break;
case Key.Enter: okButton_Click(null, null); break;
default: break;
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_okButton = base.GetTemplateChild("OkButton") as Button;
_okButton.IsEnabled = false;
_okButton.Click += new RoutedEventHandler(okButton_Click);
Button cancelButton = base.GetTemplateChild("CancelButton") as Button;
cancelButton.Click += new RoutedEventHandler(cancelButton_Click);
}
protected Button OkButton { get { return _okButton; } }
void cancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
void okButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
Button _okButton;
}
And the Generic.xaml which defines the template looks like this:
<Style TargetType="{x:Type local:ESDialogControl}">
<Setter Property="Width" Value="600" />
<Setter Property="Height" Value="Auto" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ESDialogControl}">
<Grid Height="Auto" Background="Beige" VerticalAlignment="Top" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="1" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Content="{TemplateBinding Content}" Margin="{TemplateBinding Padding}" />
<Rectangle Grid.Row="1" Fill="Navy" />
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button x:Name="OkButton" Width="70" Margin="0 10 10 0">OK</Button>
<Button x:Name="CancelButton" Width="70" Padding="2" Margin="0 10 10 0">Cancel</Button>
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Finally, I define my new dialog as a derived class:
<local:ESDialogControl x:Class="Mercersoft.Economica.Studio.View.DialogWindows.NewModelDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Mercersoft.Economica.Studio.View">
<Grid Background="Yellow" VerticalAlignment="Top" Height="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right">Name:</Label>
<TextBox x:Name="ItemName" x:FieldModifier="public" Grid.Column="1"
HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalContentAlignment="Center" TextAlignment="Left"
TextChanged="ItemName_TextChanged" />
<Label Grid.Row="1" Grid.Column="0" HorizontalAlignment="Right">Description:</Label>
<TextBox x:Name="ItemDescription" x:FieldModifier="public" Grid.Row="1" Grid.Column="1"
AcceptsReturn="True" TextWrapping="Wrap" Height="140"
HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalContentAlignment="Center" TextAlignment="Left" />
</Grid>
</local:ESDialogControl>
Everything behaves as expected, except that the main window's height is way too large. It doesn't seem to fit just its content, but rather extends almost the entire height of the monitor. I went through the code to make sure VerticalAlignments are set to Top (rather than Stretch) and that the Height properties are set to Auto except for the description text box which has an explicit height.
Which property setting am I missing to get this custom control to size correct?
You are missing the SizeToContent="Height" setting in your ESDialogControl style. You can also take away the Height="Auto" setting, as that is the default for a Window.
Related
I have a button subclass called MenuButton.
public class MenuButton : Button
{
public string Caption
{
get { return (string)GetValue(CaptionProperty); }
set { SetValue(CaptionProperty, value); }
}
public static readonly DependencyProperty CaptionProperty =
DependencyProperty.Register("Caption", typeof(string), typeof(MenuButton), new UIPropertyMetadata(null));
public UserControl Icon
{
get { return (UserControl)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register("Icon", typeof(UserControl), typeof(MenuButton),
new PropertyMetadata(null));
}
In the style I want to show an icon that was created using paths from SVG files. I have created a User Control containing the XAML for the icon:
<UserControl x:Class="WpfApplication1.Views.ScopeIcon"
.
.
.
>
<Viewbox Height="55"
Width="55">
<Grid>
<Path Fill="LightBlue" Data="M98.219,48.111C97..."/>
<Path Fill="LightBlue" Data="M98.219,46.948C97...."/>
</Grid>
</Viewbox>
</UserControl>
And here's the style:
<Style TargetType="Button"
x:Key="TestButtonStyle">
<Setter Property="Height" Value="140"/>
<Setter Property="Width" Value="195"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:MenuButton}">
<Border x:Name="TheBorder">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50*"/>
<RowDefinition Height="50*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Row="0"/> <===== THE USER CONTROL WILL GO HERE
<TextBlock Grid.Row="1"
Grid.Column="0"
Text="{TemplateBinding Caption}"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Margin="5"
Foreground="White"
FontSize="14"
TextAlignment="Center"
TextWrapping="WrapWithOverflow"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I will set it here:
<ListBox Grid.Row="0"
ItemsSource="{Binding MainTools}" >
<ListBox.ItemTemplate>
<DataTemplate>
<controls:MenuButton Caption="{Binding Caption}"
Margin="2"
Width="100"
Style="{StaticResource TestButtonStyle}"
VerticalAlignment="Top"
Command="{Binding Path=ButtonClick}"
CommandParameter="{x:Static enums:Tabs.Oscilloscope}"
Icon=""/> <============= HOW DO I PUT THE SCOPEICON USER CONTROL HERE?
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'm essentially trying to nest user controls, but I want to tell the button, in XAML, what UserControl to use for its icon.
Thank you
First, you need to update your style to set Content property on your ContentPresenter the same way you did for Text property on TextBlock.
<ContentPresenter Grid.Row="0" Content="{TemplateBinding Icon}"/>
And, then to set Icon on the MenuButton in your DataTemplate update your MenuButton element in DataTemplate to something like:
<controls:MenuButton Caption="Caption"
Margin="2"
Width="100"
Style="{StaticResource TestButtonStyle}"
VerticalAlignment="Top"
Command="{Binding Path=ButtonClick}"
CommandParameter="{x:Static enums:Tabs.Oscilloscope}">
<controls:MenuButton.Icon>
<views:ScopeIcon />
</controls:MenuButton.Icon>
</controls:MenuButton>
I need to do something like that:
It's Listbox with custom DataTemplate for elements. (I drew green a whole one element of list.)
Now I have this:
If it's real - how to change my DataTemplate(or Listbox styles) to get first resul? (Somehow to set selected area)
DataTemplate
<DataTemplate x:Key="TrackDataTemplate">
<Grid Margin="0,-5,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding FirstPlatformName}" Grid.Row="0" Grid.Column="0"/>
<Path x:Name="Node"
Margin="10,0" Grid.Row="0" Grid.Column="1"
Data="M3.1999443,36.501999 L47.300057,36.501999 47.292366,36.517475 C43.192585,44.521747 34.86105,50 25.25,50 15.638952,50 7.3074172,44.521747 3.2076359,36.517475 z M25.25,0.5 C38.919049,0.49999976 50,11.580952 50,25.25 50,27.599367 49.672657,29.87228 49.061096,32.025615 L48.919384,32.501999 1.5806161,32.501999 1.438906,32.025615 C0.82734287,29.87228 0.5000003,27.599367 0.5,25.25 0.5000003,11.580952 11.580953,0.49999976 25.25,0.5 z"
Fill="{DynamicResource white75}" Stretch="Fill"
Width="17" Height="17"/>
<Rectangle x:Name="Connection" Grid.Row="1" Grid.Column="1"
HorizontalAlignment="Center" VerticalAlignment="Top"
Fill="{DynamicResource white75}" Height="17" Width="4" Margin="10,2,10,0"/>
<TextBlock Grid.Row="1" Grid.Column="2" TextWrapping="Wrap"
Text="{Binding TsAndChannelNumber}"/>
</Grid>
</DataTemplate>
Listbox
<ListBox Grid.Column="1"
SelectedItem="{Binding SelectedTrackSegment}"
ItemsSource="{Binding SelectedTrack.Segments}"
ItemTemplate="{StaticResource TrackDataTemplate}"
Margin="30,20,30,0"/>
For complete control over the ListBoxItem you probably need to change the Template in ItemContainerStyle.
Heres a little test project to show what I mean.
NB Ive added 2 grids around your textblocks so that the Background Property can be changed using the Triggers.
Xaml
<ListBox x:Name="MyList">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Grid Margin="0,-5,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid x:Name="PlatformGrid">
<TextBlock Text="{Binding FirstPlatformName}" />
</Grid>
<Path x:Name="Node"
Margin="10,0" Grid.Row="0" Grid.Column="1"
Data="M3.1999443,36.501999 L47.300057,36.501999 47.292366,36.517475 C43.192585,44.521747 34.86105,50 25.25,50 15.638952,50 7.3074172,44.521747 3.2076359,36.517475 z M25.25,0.5 C38.919049,0.49999976 50,11.580952 50,25.25 50,27.599367 49.672657,29.87228 49.061096,32.025615 L48.919384,32.501999 1.5806161,32.501999 1.438906,32.025615 C0.82734287,29.87228 0.5000003,27.599367 0.5,25.25 0.5000003,11.580952 11.580953,0.49999976 25.25,0.5 z"
Fill="Blue" Stretch="Fill"
Width="17" Height="17"/>
<Rectangle x:Name="Connection" Grid.Row="1" Grid.Column="1"
HorizontalAlignment="Center" VerticalAlignment="Top"
Fill="Blue" Height="17" Width="4" Margin="10,2,10,0"/>
<Grid x:Name="TSGrid" Grid.Row="1" Grid.Column="2">
<TextBlock TextWrapping="Wrap" Text="{Binding TsAndChannelNumber}"/>
</Grid>
</Grid>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="True"/>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="PlatformGrid" Value="Red"/>
<Setter Property="Background" TargetName="TSGrid" Value="Red"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
MainWindows.cs
public MainWindow()
{
InitializeComponent();
Loaded += (s, args) =>
{
this.DataContext = this;
MyList.ItemsSource = LoadSegments();
};
}
private static List<Segment> LoadSegments()
{
var segments = new List<Segment>
{
new Segment { FirstPlatformName = "FPName01", TsAndChannelNumber = "TSNumber0(ChannelNumber0" },
new Segment { FirstPlatformName = "FPName01", TsAndChannelNumber = "TSNumber0(ChannelNumber1" },
new Segment { FirstPlatformName = "FPName01", TsAndChannelNumber = "TSNumber0(ChannelNumber2" },
new Segment { FirstPlatformName = "FPName01", TsAndChannelNumber = "TSNumber0(ChannelNumber3" },
new Segment { FirstPlatformName = "FPName01", TsAndChannelNumber = "TSNumber0(ChannelNumber4" }
};
return segments;
}
Segment.cs
public class Segment
{
public string FirstPlatformName { get; set; }
public string TsAndChannelNumber { get; set; }
}
You will ideally need to 'Edit a copy' of the ItemContainerStyle template in Blend so you get access to all the standard behaviours and then edit the Trigger IsSelected as above
Hope that helps
But I suggest to use two ListBox and use IsSynchronizedWithCurrentItem property to true. So when you make selection on one, it will select the same item on other automatically. And you can define your own data template for each listbox.
I have a few TextBlocks in WPF in a Grid that I would like to scale depending on their available width / height. When I searched for automatically scaling Font size the typical suggestion is to put the TextBlock into a ViewBox.
So I did this:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Viewbox MaxHeight="18" Grid.Column="0" Stretch="Uniform" Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock Text="{Binding Text1}" />
</Viewbox>
<Viewbox MaxHeight="18" Grid.Column="1" Stretch="Uniform" Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock Text="{Binding Text2}" />
</Viewbox>
<Viewbox MaxHeight="18" Grid.Column="2" Stretch="Uniform" Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock Text="{Binding Text3}" />
</Viewbox>
</Grid>
And it scales the font for each TextBlock automatically. However, this looks funny because if one of the TextBlocks has longer text then it will be in a smaller font while it's neighboring grid elements will be in a larger font. I want the Font size to scale by group, perhaps it would be nice if I could specify a "SharedSizeGroup" for a set of controls to auto size their font.
e.g.
The first text blocks text might be "3/26/2013 10:45:30 AM", and the second TextBlocks text might say "FileName.ext". If these are across the width of a window, and the user begins resizing the window smaller and smaller. The date will start making its font smaller than the file name, depending on the length of the file name.
Ideally, once one of the text fields starts to resize the font point size, they would all match. Has anyone came up with a solution for this or can give me a shot at how you would make it work? If it requires custom code then hopefully we / I could repackage it into a custom Blend or Attached Behavior so that is re-usable for the future. I think it is a pretty general problem, but I wasn't able to find anything on it by searching.
Update
I tried Mathieu's suggestion and it sort of works, but it has some side-effects:
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="270" Width="522">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="SkyBlue" />
<Viewbox Grid.Row="1" MaxHeight="30" Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="SomeLongText" Margin="5" />
<TextBlock Grid.Column="1" Text="TextA" Margin="5" />
<TextBlock Grid.Column="2" Text="TextB" Margin="5" />
</Grid>
</Viewbox>
</Grid>
</Window>
Honestly, missing hte proportional columns is probably fine with me. I wouldn't mind it AutoSizing the columns to make smart use of the space, but it has to span the entire width of the window.
Notice without maxsize, in this extended example the text is too large:
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="270" Width="522">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="SkyBlue" />
<Viewbox Grid.Row="1" Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="SomeLongText" Margin="5" />
<TextBlock Grid.Column="1" Text="TextA" Margin="5" />
<TextBlock Grid.Column="2" Text="TextB" Margin="5" />
</Grid>
</Viewbox>
</Grid>
Here, I would want to limit how big the font can get, so it doesn't waste vertical window real estate. I'm expecting the output to be aligned left, center, and right with the Font being as big as possible up to the desired maximum size.
#adabyron
The solution you propose is not bad (And is the best yet) but it does have some limitations. For example, initially I wanted my columns to be proportional (2nd one should be centered). For example, my TextBlocks might be labeling the start, center, and stop of a graph where alignment matters.
<Window x:Class="WpfApplication6.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:b="clr-namespace:WpfApplication6.Behavior"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="SkyBlue" />
<Line X1="0.5" X2="0.5" Y1="0" Y2="1" Stretch="Fill" StrokeThickness="3" Stroke="Red" />
<Grid Grid.Row="1">
<i:Interaction.Behaviors>
<b:MoveToViewboxBehavior />
</i:Interaction.Behaviors>
<Viewbox Stretch="Uniform" />
<ContentPresenter >
<ContentPresenter.Content>
<Grid x:Name="TextBlockContainer">
<Grid.Resources>
<Style TargetType="TextBlock" >
<Setter Property="FontSize" Value="16" />
<Setter Property="Margin" Value="5" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="SomeLongText" VerticalAlignment="Center" HorizontalAlignment="Center" />
<TextBlock Grid.Column="2" Text="TextA" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBlock Grid.Column="4" Text="TextB" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ContentPresenter.Content>
</ContentPresenter>
</Grid>
</Grid>
</Window>
And here is the result. Notice it does not know that it is getting clipped early on, and then when it substitutes ViewBox it looks as if the Grid defaults to column size "Auto" and no longer aligns center.
I wanted to edit the answer I had already offered, but then decided it makes more sense to post a new one, because it really depends on the requirements which one I'd prefer. This here probably fits Alan's idea better, because
The middle textblock stays in the middle of the window
Fontsize adjustment due to height clipping is accomodated
Quite a bit more generic
No viewbox involved
The other one has the advantage that
Space for the textblocks is allocated more efficiently (no unnecessary margins)
Textblocks may have different fontsizes
I tested this solution also in a top container of type StackPanel/DockPanel, behaved decently.
Note that by playing around with the column/row widths/heights (auto/starsized), you can get different behaviors. So it would also be possible to have all three textblock columns starsized, but that means width clipping does occur earlier and there is more margin. Or if the row the grid resides in is auto sized, height clipping will never occur.
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:beh="clr-namespace:WpfApplication1.Behavior"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.9*"/>
<RowDefinition Height="0.1*" />
</Grid.RowDefinitions>
<Rectangle Fill="DarkOrange" />
<Grid x:Name="TextBlockContainer" Grid.Row="1" >
<i:Interaction.Behaviors>
<beh:ScaleFontBehavior MaxFontSize="32" />
</i:Interaction.Behaviors>
<Grid.Resources>
<Style TargetType="TextBlock" >
<Setter Property="Margin" Value="5" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="SomeLongText" />
<TextBlock Grid.Column="1" Text="TextA" HorizontalAlignment="Center" />
<TextBlock Grid.Column="2" Text="TextB" HorizontalAlignment="Right" />
</Grid>
</Grid>
</Window>
ScaleFontBehavior:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
using WpfApplication1.Helpers;
namespace WpfApplication1.Behavior
{
public class ScaleFontBehavior : Behavior<Grid>
{
// MaxFontSize
public double MaxFontSize { get { return (double)GetValue(MaxFontSizeProperty); } set { SetValue(MaxFontSizeProperty, value); } }
public static readonly DependencyProperty MaxFontSizeProperty = DependencyProperty.Register("MaxFontSize", typeof(double), typeof(ScaleFontBehavior), new PropertyMetadata(20d));
protected override void OnAttached()
{
this.AssociatedObject.SizeChanged += (s, e) => { CalculateFontSize(); };
}
private void CalculateFontSize()
{
double fontSize = this.MaxFontSize;
List<TextBlock> tbs = VisualHelper.FindVisualChildren<TextBlock>(this.AssociatedObject);
// get grid height (if limited)
double gridHeight = double.MaxValue;
Grid parentGrid = VisualHelper.FindUpVisualTree<Grid>(this.AssociatedObject.Parent);
if (parentGrid != null)
{
RowDefinition row = parentGrid.RowDefinitions[Grid.GetRow(this.AssociatedObject)];
gridHeight = row.Height == GridLength.Auto ? double.MaxValue : this.AssociatedObject.ActualHeight;
}
foreach (var tb in tbs)
{
// get desired size with fontsize = MaxFontSize
Size desiredSize = MeasureText(tb);
double widthMargins = tb.Margin.Left + tb.Margin.Right;
double heightMargins = tb.Margin.Top + tb.Margin.Bottom;
double desiredHeight = desiredSize.Height + heightMargins;
double desiredWidth = desiredSize.Width + widthMargins;
// adjust fontsize if text would be clipped vertically
if (gridHeight < desiredHeight)
{
double factor = (desiredHeight - heightMargins) / (this.AssociatedObject.ActualHeight - heightMargins);
fontSize = Math.Min(fontSize, MaxFontSize / factor);
}
// get column width (if limited)
ColumnDefinition col = this.AssociatedObject.ColumnDefinitions[Grid.GetColumn(tb)];
double colWidth = col.Width == GridLength.Auto ? double.MaxValue : col.ActualWidth;
// adjust fontsize if text would be clipped horizontally
if (colWidth < desiredWidth)
{
double factor = (desiredWidth - widthMargins) / (col.ActualWidth - widthMargins);
fontSize = Math.Min(fontSize, MaxFontSize / factor);
}
}
// apply fontsize (always equal fontsizes)
foreach (var tb in tbs)
{
tb.FontSize = fontSize;
}
}
// Measures text size of textblock
private Size MeasureText(TextBlock tb)
{
var formattedText = new FormattedText(tb.Text, CultureInfo.CurrentUICulture,
FlowDirection.LeftToRight,
new Typeface(tb.FontFamily, tb.FontStyle, tb.FontWeight, tb.FontStretch),
this.MaxFontSize, Brushes.Black); // always uses MaxFontSize for desiredSize
return new Size(formattedText.Width, formattedText.Height);
}
}
}
VisualHelper:
public static List<T> FindVisualChildren<T>(DependencyObject obj) where T : DependencyObject
{
List<T> children = new List<T>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var o = VisualTreeHelper.GetChild(obj, i);
if (o != null)
{
if (o is T)
children.Add((T)o);
children.AddRange(FindVisualChildren<T>(o)); // recursive
}
}
return children;
}
public static T FindUpVisualTree<T>(DependencyObject initial) where T : DependencyObject
{
DependencyObject current = initial;
while (current != null && current.GetType() != typeof(T))
{
current = VisualTreeHelper.GetParent(current);
}
return current as T;
}
Put your grid in the ViewBox, which will scale the whole Grid :
<Viewbox Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Text1}" Margin="5" />
<TextBlock Grid.Column="1" Text="{Binding Text2}" Margin="5" />
<TextBlock Grid.Column="2" Text="{Binding Text3}" Margin="5" />
</Grid>
</Viewbox>
I think I know the way to go and will leave the rest to you. In this example, I bound the FontSize to the ActualHeight of the TextBlock, using a converter (the converter is below):
<Window x:Class="MyNamespace.Test"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Converters="clr-namespace:UpdateYeti.Converters"
Title="Test" Height="570" Width="522">
<Grid Height="370" Width="522">
<Grid.Resources>
<Converters:HeightToFontSizeConverter x:Key="conv" />
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="SkyBlue" />
<Grid Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinHeight="60" Background="Beige">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="SomeLongText" Margin="5"
FontSize="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight, Converter={StaticResource conv}}" />
<TextBlock Grid.Column="1" Text="TextA" Margin="5" HorizontalAlignment="Center"
FontSize="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight, Converter={StaticResource conv}}" />
<TextBlock Grid.Column="2" Text="TextB" Margin="5" FontSize="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight, Converter={StaticResource conv}}" />
</Grid>
</Grid>
</Window>
[ValueConversion(typeof(double), typeof(double))]
class HeightToFontSizeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// here you can use the parameter that you can give in here via setting , ConverterParameter='something'} or use any nice login with the VisualTreeHelper to make a better return value, or maybe even just hardcode some max values if you like
var height = (double)value;
return .65 * height;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
General remark: A possible alternative to the whole text scaling could be to just use TextTrimming on the TextBlocks.
I've struggled to find a solution to this one. Using a viewbox is really hard to mix with any layout adjustments. Worst of all, ActualWidth etc. do not change inside a viewbox. So I finally decided to use the viewbox only if absolutely necessary, which is when clipping would occur. I'm therefore moving the content between a ContentPresenter and a Viewbox, depending upon the available space.
This solution is not as generic as I would like, mainly the MoveToViewboxBehavior does assume it is attached to a grid with the following structure. If that cannot be accomodated, the behavior will most likely have to be adjusted. Creating a usercontrol and denoting the necessary parts (PART_...) might be a valid alternative.
Note that I have extended the grid's columns from three to five, because that makes the solution a lot easier. It means that the middle textblock will not be exactly in the middle, in the sense of absolute coordinates, instead it is centered between the textblocks to the left and right.
<Grid > <!-- MoveToViewboxBehavior attached to this grid -->
<Viewbox />
<ContentPresenter>
<ContentPresenter.Content>
<Grid x:Name="TextBlockContainer">
<TextBlocks ... />
</Grid>
</ContentPresenter.Content>
</ContentPresenter>
</Grid>
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:beh="clr-namespace:WpfApplication1.Behavior"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="SkyBlue" />
<Grid Grid.Row="1">
<i:Interaction.Behaviors>
<beh:MoveToViewboxBehavior />
</i:Interaction.Behaviors>
<Viewbox Stretch="Uniform" />
<ContentPresenter >
<ContentPresenter.Content>
<Grid x:Name="TextBlockContainer">
<Grid.Resources>
<Style TargetType="TextBlock" >
<Setter Property="FontSize" Value="16" />
<Setter Property="Margin" Value="5" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="SomeLongText" />
<TextBlock Grid.Column="2" Text="TextA" />
<TextBlock Grid.Column="4" Text="TextB" />
</Grid>
</ContentPresenter.Content>
</ContentPresenter>
</Grid>
</Grid>
</Window>
MoveToViewBoxBehavior:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
using WpfApplication1.Helpers;
namespace WpfApplication1.Behavior
{
public class MoveToViewboxBehavior : Behavior<Grid>
{
// IsClipped
public bool IsClipped { get { return (bool)GetValue(IsClippedProperty); } set { SetValue(IsClippedProperty, value); } }
public static readonly DependencyProperty IsClippedProperty = DependencyProperty.Register("IsClipped", typeof(bool), typeof(MoveToViewboxBehavior), new PropertyMetadata(false, OnIsClippedChanged));
private static void OnIsClippedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var beh = (MoveToViewboxBehavior)sender;
Grid grid = beh.AssociatedObject;
Viewbox vb = VisualHelper.FindVisualChild<Viewbox>(grid);
ContentPresenter cp = VisualHelper.FindVisualChild<ContentPresenter>(grid);
if ((bool)e.NewValue)
{
// is clipped, so move content to Viewbox
UIElement element = cp.Content as UIElement;
cp.Content = null;
vb.Child = element;
}
else
{
// can be shown without clipping, so move content to ContentPresenter
cp.Content = vb.Child;
vb.Child = null;
}
}
protected override void OnAttached()
{
this.AssociatedObject.SizeChanged += (s, e) => { IsClipped = CalculateIsClipped(); };
}
// Determines if the width of all textblocks within TextBlockContainer (using MaxFontSize) are wider than the AssociatedObject grid
private bool CalculateIsClipped()
{
double totalDesiredWidth = 0d;
Grid grid = VisualHelper.FindVisualChildByName<Grid>(this.AssociatedObject, "TextBlockContainer");
List<TextBlock> tbs = VisualHelper.FindVisualChildren<TextBlock>(grid);
foreach (var tb in tbs)
{
if (tb.TextWrapping != TextWrapping.NoWrap)
return false;
totalDesiredWidth += MeasureText(tb).Width + tb.Margin.Left + tb.Margin.Right + tb.Padding.Left + tb.Padding.Right;
}
return Math.Round(this.AssociatedObject.ActualWidth, 5) < Math.Round(totalDesiredWidth, 5);
}
// Measures text size of textblock
private Size MeasureText(TextBlock tb)
{
var formattedText = new FormattedText(tb.Text, CultureInfo.CurrentUICulture,
FlowDirection.LeftToRight,
new Typeface(tb.FontFamily, tb.FontStyle, tb.FontWeight, tb.FontStretch),
tb.FontSize, Brushes.Black);
return new Size(formattedText.Width, formattedText.Height);
}
}
}
VisualHelper:
public static class VisualHelper
{
public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
{
T child = null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var o = VisualTreeHelper.GetChild(obj, i);
if (o != null)
{
child = o as T;
if (child != null) break;
else
{
child = FindVisualChild<T>(o); // recursive
if (child != null) break;
}
}
}
return child;
}
public static List<T> FindVisualChildren<T>(DependencyObject obj) where T : DependencyObject
{
List<T> children = new List<T>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var o = VisualTreeHelper.GetChild(obj, i);
if (o != null)
{
if (o is T)
children.Add((T)o);
children.AddRange(FindVisualChildren<T>(o)); // recursive
}
}
return children;
}
public static T FindVisualChildByName<T>(DependencyObject parent, string name) where T : FrameworkElement
{
T child = default(T);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var o = VisualTreeHelper.GetChild(parent, i);
if (o != null)
{
child = o as T;
if (child != null && child.Name == name)
break;
else
child = FindVisualChildByName<T>(o, name);
if (child != null) break;
}
}
return child;
}
}
You can use a hidden ItemsControl in a ViewBox.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Viewbox VerticalAlignment="Bottom">
<Grid>
<TextBlock Text="SomeLongText"/>
<ItemsControl Visibility="Hidden">
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
<TextBlock Text="SomeLongText"/>
<TextBlock Text="TextA"/>
<TextBlock Text="TextB"/>
</ItemsControl>
</Grid>
</Viewbox>
<Viewbox Grid.Column="1" VerticalAlignment="Bottom">
<Grid>
<TextBlock Text="TextA"/>
<ItemsControl Visibility="Hidden">
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
<TextBlock Text="SomeLongText"/>
<TextBlock Text="TextA"/>
<TextBlock Text="TextB"/>
</ItemsControl>
</Grid>
</Viewbox>
<Viewbox Grid.Column="2" VerticalAlignment="Bottom">
<Grid>
<TextBlock Text="TextB"/>
<ItemsControl Visibility="Hidden">
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
<TextBlock Text="SomeLongText"/>
<TextBlock Text="TextA"/>
<TextBlock Text="TextB"/>
</ItemsControl>
</Grid>
</Viewbox>
</Grid>
or
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Viewbox VerticalAlignment="Bottom">
<Grid>
<TextBlock Text="{Binding Text1}"/>
<ItemsControl Visibility="Hidden" ItemsSource="{Binding AllText}">
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Viewbox>
<Viewbox Grid.Column="1" VerticalAlignment="Bottom">
<Grid>
<TextBlock Text="{Binding Text2}"/>
<ItemsControl Visibility="Hidden" ItemsSource="{Binding AllText}">
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Viewbox>
<Viewbox Grid.Column="2" VerticalAlignment="Bottom">
<Grid>
<TextBlock Text="{Binding Text3}"/>
<ItemsControl Visibility="Hidden" ItemsSource="{Binding AllText}">
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Viewbox>
</Grid>
A solution could be something like that :
Choose a maxFontSize, then define the appropriate FontSize to be displayed considering the current Window by using a linear equation. Window's height or width would limit the final FontSize choice.
Let's take the case of a "single kind TextBlock" for the whole grid :
Window.Current.SizeChanged += (sender, args) =>
{
int minFontSize = a;
int maxFontSize = b;
int maxMinFontSizeDiff = maxFontSize - minFontSize;
int gridMinHeight = c;
int gridMaxHeight = d;
int gridMaxMinHeightDiff = gridMaxHeight - gridMinHeight;
int gridMinWidth = e;
int gridMaxWidth = f;
int gridMaxMinHeightDiff = gridMaxWidth - gridMaxWidth;
//Linear equation considering "max/min FontSize" and "max/min GridHeight/GridWidth"
double heightFontSizeDouble = (maxMinFontSizeDiff / gridMaxMinHeightDiff ) * Grid.ActualHeight + (maxFontSize - (gridMaxHeight * (maxMinFontSizeDiff / gridMaxMinHeightDiff)))
double widthFontSizeDouble = (maxMinFontSizeDiff / gridMaxMinWidthDiff ) * Grid.ActualWidth + (maxFontSize - (gridMaxWidth * (maxMinFontSizeDiff / gridMaxMinWidthDiff)))
int heightFontSize = (int)Math.Round(heightFontSizeDouble)
int widthFontSize = (int)Math.Round(widthFontSizeDouble)
foreach (var children in Grid.Children)
{
(children as TextBlock).FontSize = Math.Min(heightFontSize, widthFontSize);
}
}
I use ScaleTransform. This scales the current font by the desired amount:
<ScaleTransform x:Key="FontDoubled" ScaleX="2" ScaleY="2" />
<ScaleTransform x:Key="FontHalved" ScaleX="0.5" ScaleY="0.5" />
The larger the ScaleX and ScaleY, the larger the font is scaled by. No need to change individual font sizes.
It can also be used with just ScaleX or ScaleY to change the font size in one direction only, or with different values.
To use it:
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="4"
FontWeight="Bold"
Content="This is normal the size" />
...gives:
And:
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="4"
FontWeight="Bold"
LayoutTransform="{StaticResource FontDoubled}"
Content="This is Double size" />
...gives:
I'm trying to create a tab control in which the Header on the TabItem is binded in a textbox that is in the controltemplate of the tabcontrol. how can i do this through binding in the style?
Here's my code:
<Style x:Key="TabControlTest1" TargetType="TabControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!--Area for TabItems-->
<Border Grid.Column="0"
Grid.Row="0"
>
<TabPanel IsItemsHost="True"
x:Name="HeaderPanel"
Background="Transparent" />
</Border>
<!--Content of SelectedItems-->
<Border Grid.Column="1"
BorderBrush="{DynamicResource TabControlContentPresentBorderBrush}"
BorderThickness="0,1,1,1"
Background="{DynamicResource TabControlContentPresentBackgroundBrush}"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--///This is Where I want to bind the Header///-->
<Label
Grid.Row="0"
Foreground="AliceBlue"
Content="{Binding Header, ElementName=TabItem}"/>
<ContentPresenter
Grid.Row="1"
x:Name="PART_SelectedContentHost"
ContentSource="SelectedContent"
Margin="5"/>
</Grid>
</Border>
<ControlTemplate x:Key="TabItemControlTemplate" TargetType="{x:Type TabItem}">
<!--Grid Defines Height and also hold content header-->
<Grid>
<Border Background="{DynamicResource TabItemContentPresentBackgroundBrush}"
Margin="0,0,0,5">
<!--Content of TabItem will be rendered-->
<ContentPresenter
x:Name="ContentSite"
Margin="3"
HorizontalAlignment="Left"
VerticalAlignment="Center"
RecognizesAccessKey="True"
ContentSource="Header"/>
</Border>
</Grid>
</ControlTemplate>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
try this
<Label Grid.Row="0"
Foreground="AliceBlue"
Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabControl}
, Path=SelectedItem.Header}"/>
You could try and achieve this either using TemplateBinding or using RelativeSource.
TemplateBinding:
<Label Grid.Row="0"
Foreground="AliceBlue"
Content="{TemplateBinding Header}"/>
RelativeSource:
<Label Grid.Row="0"
Foreground="AliceBlue"
Content="{Binding Path=Header, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabItem}}"/>
This isn't using a control template, but demonstrates binding a textbox to the tab header through a view model. Note that I'm using MVVM light (ViewModelBase and Set()), but you can replace with your own INotifyPropertyChanged support if necessary.
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ContentTemplate" DataType="{x:Type Samples:TabBindingViewModel}">
<StackPanel>
<TextBlock Text="{Binding MyContent}"/>
<TextBox Text="{Binding Header}"/>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<TabControl
ContentTemplate="{StaticResource ContentTemplate}"
DisplayMemberPath="Header"
ItemsSource="{Binding Items}" />
</Grid>
public class TabContentToHeaderViewModels : ViewModelBase
{
private readonly ObservableCollection<TabContentToHeaderViewModel> _items;
public TabContentToHeaderViewModels()
{
_items = new ObservableCollection<TabContentToHeaderViewModel>
{
new TabContentToHeaderViewModel(1),
new TabContentToHeaderViewModel(2),
new TabContentToHeaderViewModel(3),
};
}
public IEnumerable<TabContentToHeaderViewModel> Items
{
get { return _items; }
}
}
public class TabContentToHeaderViewModel : ViewModelBase
{
public TabContentToHeaderViewModel() : this(0)
{
}
public TabContentToHeaderViewModel(int n)
{
Header = "I'm the header: " + n.ToString(CultureInfo.InvariantCulture);
MyContent = "I'm the content: " + n.ToString(CultureInfo.InvariantCulture);
}
private string _header;
public string Header
{
get { return _header; }
set { Set(() => Header, ref _header, value); }
}
public string MyContent { get; set; }
}
In my project lots of views must have Ok,Cancel button to bottom of view. So i want create a base control. And add Ok, Cancel button to bottom of this control. Then i will inherite this control. In the inherited control i want to ad a textbox to near this buttons. How can i do this?
I believe there is a template for the base control.Is so in the inherited control's template edit the button panel and add textbox.
You can create Control which is inherited from ContentControl and define your buttons in ControlTemplate of it. Then you can just use this control as shown in sample.
ControlWithButtons.cs
[TemplatePart(Name="btnOk", Type= typeof(Button))]
public class ControlWithButtons : ContentControl
{
public ControlWithButtons()
{
this.DefaultStyleKey = typeof(ControlWithButtons);
}
Button _btnOk;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_btnOk = GetTemplateChild("btnOk") as Button;
if (_btnOk != null)
{
// do what you want with you button
_btnOk.Click += new RoutedEventHandler(_btnOk_Click);
}
}
void _btnOk_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Ok button clicked");
}
}
Generic.xaml (must be in (ProjectDir)/Themes/Generic.xaml)
(do not forget xmlns:local="clr-namespace:Test")
<Style TargetType="local:ControlWithButtons">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ControlWithButtons">
<Border Background="Yellow" CornerRadius="5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Border Margin="10" Background="LightGray">
<ContentPresenter/>
</Border>
<Button Grid.Row="1" x:Name="btnOk" Content="OK" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="5"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Using your control:
<Grid x:Name="LayoutRoot" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<local:ControlWithButtons Width="300" Height="250" HorizontalAlignment="Center" VerticalAlignment="Center">
<!-- TextBox is put into control -->
<TextBox Width="200" Height="Auto" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="5" />
<!-- You can also specify ContentTemplate for ControlWithButtons -->
<!-- (see in MSDN "http://msdn.microsoft.com/en-us/library/system.windows.controls.contentcontrol.contenttemplate(v=vs.95).aspx") -->
</local:ControlWithButtons>
</Grid>
I found the solution. [ContentProperty("")] attribute solved my problem.
Like this:
Base Xaml
<Grid Name="layoutRoot" VerticalAlignment="Stretch" >
<Grid.RowDefinitions>
<RowDefinition Height="289*" />
<RowDefinition Height="51" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Grid.Column="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Orientation="Horizontal">
<Button Name="btnOk" Content="Ok" Height="23" Width="75" HorizontalAlignment="Left" Margin="10,10,10,10" Command="{Binding Path=OnApply, Mode=OneWay}"/>
<Button Name="btnCancel" Content="Cancel" Height="23" Width="75" HorizontalAlignment="Left" Margin="10,10,10,10"/>
</StackPanel>
<Grid Name="rootContent" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Grid.Column="0" Grid.Row="0" />
</Grid>
</sdk:ChildWindow>
Base CodeBehind
[ContentProperty("RootContentControl")]//This attribute solved my problem.
public partial class BaseView : ChildWindow
{
public BaseView()
{
InitializeComponent();
}
public UIElementCollection RootContentControl
{
get { return rootContent.Children; }
}
}
Inherited Xaml
<Views:BaseView x:Class="MvvmLight1.Views.InheritedView"
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:Views="clr-namespace:MvvmLight1.Views"
xmlns:viewModels="clr-namespace:MvvmLight1.ViewModel"
mc:Ignorable="d" d:DesignHeight="244" d:DesignWidth="392"
DataContext="{Binding Source=viewModels:InheritedViewModel}">
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
</Grid>
</Views:BaseView>
Inherited CodeBehind
public partial class InheritedView : BaseView
{
public InheritedView()
{
InitializeComponent();
}
}