Constrain WPF element width inside its container - wpf

I am placing a templated Button with some content in inside it (both Image and TextBlocks) in some other control's Grid:
<SomeControl>
<Grid>
<SpecialButton/>
</Grid>
</SomeControl>
<Style TargetType="{x:Type local:SpecialButton}">>
...
<Setter Property="Template">
<Setter.Value>
...
<TextBlock/>
<TextBlock/>
</Setter.Value>
</Style>
The "SomeControl" control's width is dynamic - that is - it can be dynamically changed according to the screen width, it's container width, etc. Hence I use another method to calculate the "SpecialButton"'s width.
As long as the TextBlock within the button is not wider then the button itself The "SpecialButton" is being perfectly matching its size to it's "SomeControl" container.
However, if the TextBlock's text is longer then the width of the "SpecialButton", the "SpecialButton" right border disappears and the button looks really bad. That being said, the "SpecialButton"'s width is still constained to its containers' width although drawing is not being well calculated.
I am trying to find a way to constain the TextBlock's (or even better the complete Grid's width) without defining an absoulte width, so the "SpecialButton" will still get drawn well and ignore it's children's overflow. Something like CSS's {overflow:hidden} would have been great...
Note:
I can't allow wrapping since the "SpecialButton" height is also constained (and manually calculated).

Width (and height) in WPF elements is pretty straightforward once you get used to it. Your grid, by default is set to Auto width, which means it will request 100% of the width of the parent.
Instead of setting a maximum size, I generally put items that I want dynamically sized and positioned in a grid and set grid row and column sizes. So if I want something to take up 1/3 of the available horizontal space, I can have a grid with two columns, one with width set to "1*" and the other set to "2*" (2/3 being twice the size of 1/3), and put my item in column 1 set to take up all available width.
In your case, the text needs to be truncated as it is doing its best to render all of the text, and is extending the button past the bounds of it's container to do it. There are two options for the built in trimming capability. TextTrimming="CharacterEllipsis" and TextTrimming="WordEllipsis" Just put that in your textblocks. I would put the textblocks in a container of some kind to keep them separate. If the first has content that is long enough, it may push the other completely off the side.
Element Sizing
<Window
x:Class="MainWindow"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480">
<Grid x:Name="LayoutRoot" DataContext="{Binding}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<local:SomeControl Margin="8,8,8,0"/>
</Grid>
</Window>
Text Trimming
<ControlTemplate x:Key="ButtonBaseControlTemplate1" TargetType="{x:Type ButtonBase}">
<Microsoft_Windows_Themes:ButtonChrome x:Name="Chrome" BorderBrush="{TemplateBinding BorderBrush}" Fill="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderPressed="{TemplateBinding IsPressed}" RenderDefaulted="{TemplateBinding Button.IsDefaulted}" SnapsToDevicePixels="True" ThemeColor="Metallic">
<TextBlock Text="{TemplateBinding Content}" TextTrimming="WordEllipsis" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Microsoft_Windows_Themes:ButtonChrome>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="RenderDefaulted" TargetName="Chrome" Value="True"/>
</Trigger>
<Trigger Property="ToggleButton.IsChecked" Value="True">
<Setter Property="RenderPressed" TargetName="Chrome" Value="True"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Additionally, you might want to set the tooltip of your textboxes to the value of the text also. If the text is truncated and a user want to see what it is, they can mouse over the button and find out.

Related

How to get the content width of the TextBox except for width of the VerticalScrollBar?

I created HighlightTextBox that derived TextBox. And the code is as shown below.
<Style TargetType="{x:Type host:HighlightTextBox}">
<Setter Property="AcceptsReturn" Value="True" />
<Setter Property="HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="TextWrapping" Value="NoWrap"/>
<Setter Property="Foreground" Value="#00000000"/>
<Setter Property="FontSize" Value="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=FontSize, Mode=TwoWay}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate x:Name="textArea" TargetType="{x:Type host:HighlightTextBox}">
<Border BorderThickness="{Binding BorderTickness}"
BorderBrush="{Binding BorderBrush}"
Background="{Binding BackGround}">
<Grid Margin="{TemplateBinding Padding}" x:Name="PART_Grid">
<host:TextCanvas x:Name="PART_RenderCanvas" ClipToBounds="True"
TextOptions.TextRenderingMode="ClearType" TextOptions.TextFormattingMode="Display"
LineHeight="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=LineHeight}"/>
<ScrollViewer x:Name="PART_ContentHost" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The important point is ControlTemplate.
As you can see, the content of the HighlightTextBox consists of TextCanvas and ScrollViewer.
The HighlightTextBox highlights the currently selected line by painting on the TextCanvas but it invasion the VerticalScrollViewer section as below.
I want to display it by not invasion the VerticalScrollViewer.
I think that maybe the cause is TextCanvas occupy entire section of TextBox.
So I tried to move TextCanvas into the ScrollViewer but this way make facing the run-time error that "PART_ContentHost can't have child element.
I think that another way is to get the content width of the TextBox except for the width of the VerticalScrollBar and binding it to the width of the TextCanvas. But I don't know how to get it.
What I should do to solve this problem?
If you have a better way or another way to solve then please let me know.
Thank you for reading.
Search the visual tree for your ScrollViewer's content presenter, that will give you the width of the content area itself:
var scrollViewer = yourTextBox.Template.FindName("PART_ContentHost", yourTextBox) as ScrollViewer;
var contentPresenter = UIHelper.FindChild<ScrollContentPresenter>(scrollViewer, String.Empty);
var width = contentPresenter.ActualWidth;
UPDATE: You can bind to the ScrollContentPresenter's content control directly like this:
<Grid Margin="{TemplateBinding Padding}" x:Name="PART_Grid">
<TextBlock Text="{Binding ElementName=PART_ContentHost, Path=Content.ActualWidth}" Background="CornflowerBlue" VerticalAlignment="Top"/>
<ScrollViewer x:Name="PART_ContentHost" />
</Grid>
Keep in mind though that this gives you the area of the ScrollViewers content width which, in the example I've given above, will a bit smaller than the TextBlock's width due to the fact that the ScrollViewers content is a TextBoxView with a 2,0,2,0 margin:
To compensate for this you'll probably want to bind your Canvas margin to the TextBoxView margin (which in my case is a TextBlock rather than a Canvas):
<Grid Margin="{TemplateBinding Padding}" x:Name="PART_Grid">
<TextBlock Text="{Binding ElementName=PART_ContentHost, Path=Content.ActualWidth}"
Margin="{Binding ElementName=PART_ContentHost, Path=Content.Margin}"
Background="CornflowerBlue" VerticalAlignment="Top" />
<ScrollViewer x:Name="PART_ContentHost" Padding="0" Margin="0"/>
</Grid>
This will keep your Canvas's alignment in the parent Grid the same as for the TextBoxView so that everything lines up properly:
(You could also just remove the TextBoxView's margin, but that's probably not what you want).

Why is my Fluent Window title staying centered?

I'm using the latest version of Fluent.Ribbon. I've been doing some styling, most of which requires completely replacing the Styles and ControlTemplates, but I've hit a snag. The title of my app is centered in the header bar and I can't get it to move to the left.
My visual tree looks like this:
MainWindow
Grid
Adorner
Grid
DockPanel
PART_Icon
PART_RibbonTitleBar
Grid
PART_HeaderHolder [ContentPresenter]
TextBlock
PART_ItemsContainer
PART_QuickAccessToolbarHolder
I copied the current version of the Fluent:RibbonTitleBar ControlTemplate and Style into my override xaml for modification, but nothing I do makes any difference (yes it is loading my overriding styles.)
When I use the inspector tool in the app, the only elements I can highlight are the innermost TextBlock, which fits the text exactly with no stretch, and the DockPanel several levels above, which stretches the full window width. In the original window ControlTemplate, which you can see here, The RibbonTitleBar is the last element of the DockPanel which has LastChildFill set. The RibbonTitleBar does have a RenderSize of the full width, but then the Grid below it has a RenderSize of 0,0. Then PART_HeaderHolder inside that has a RenderSize that exactly covers the title text.
It doesn't seem to matter if I set HorizontalAlignment on various elements to Left or Stretch. I also tried changing the innermost Grid to other container types such as DockPanel and StackPanel. Nothing changes anything about the layout.
Here's my style overrides for the RibbonTitleBar. The only change I've made is that I moved the QuickAccessToolbar to the end and permanently collapsed it (if I try deleting it, the app crashes looking for it) and I tried defining some columns on the inner Grid to no avail.
<Style TargetType="{x:Type Fluent:RibbonTitleBar}">
<Setter Property="Template"
Value="{DynamicResource RibbonTitleBarControlOverride}" />
<Setter Property="Focusable"
Value="False" />
<Setter Property="VerticalAlignment"
Value="Top" />
<Setter Property="HorizontalAlignment"
Value="Stretch" />
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Margin="-2,0"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
Text="{Binding}"
TextWrapping="NoWrap"
TextTrimming="CharacterEllipsis" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="RibbonTitleBarControlOverride"
TargetType="{x:Type Fluent:RibbonTitleBar}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0" x:Name="PART_HeaderHolder"
HorizontalAlignment="Left"
ContentSource="Header"
IsHitTestVisible="False" />
<Fluent:RibbonContextualGroupsContainer Grid.Column="1" x:Name="PART_ItemsContainer"
IsItemsHost="True" />
<ContentPresenter x:Name="PART_QuickAccessToolbarHolder"
ContentSource="QuickAccessToolBar" Visibility="Collapsed" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsCollapsed"
Value="True">
<Setter Property="Visibility"
Value="Collapsed"
TargetName="PART_ItemsContainer" />
</Trigger>
<Trigger Property="HideContextTabs"
Value="True">
<Setter Property="Visibility"
Value="Collapsed"
TargetName="PART_ItemsContainer" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

Remove shadow effect when window is maximised in wpf

I had an window with styles
Background="Transparent", AllowsTransparency="True", WindowStyle="None", ResizeMode="NoResize".
I had placed custom min, max, close buttons and implemented functionality.And as i want the window to resize the window on all the sides. i implemented it with HwndSource which works fine. Now my concern is i want shadow effect for the window.so i placed the border with shadow effect like this.
<Border CornerRadius="5" Margin="10">
<Border.BitmapEffect>
<DropShadowBitmapEffect ShadowDepth="10" Opacity=".5" Softness="9" />
</Border.BitmapEffect>
<Grid> **Content over here**</Grid>
</Border>
window gets the shadow effect. but when i maximise the window. it does not fit to the screen as i had added border. i need to remove the shadow effect when window is maximized so that it fit to the screen. and the shadow effect should appear when window is in not in maximized state. how to get this or is there any another method to get this.. please help me to find the solution.
I think it would be possible to add/remove the shadow effect dynamically in the code by creating style for your Border. You can create a style for your border something like this:
<Style x:Key="borderstyle" TargetType="Border">
<Setter Property="BitmapEffect">
<Setter.Value>
<DropShadowBitmapEffect ShadowDepth="10"> </DropShadowBitmapEffect>
</Setter.Value>
</Setter>
</Style>
And then set it dynamically in the code
borderelement.Style =(Style)Application.Current.MainWindow.Resources["borderstyle"];
and remove it by using the following code
borderelement.Style = null
You can use a trigger to determine if Window is maximized and then in the trigger setter, remove the effect. Just overload template for the Window and inside ControlTemplate triggers add a trigger
<Window.Template>
<ControlTemplate TargetType="Window">
<Border x:Name="brd">
<Border.Effect>
<DropShadowEffect BlurRadius="20" Color="Black"/>
</Border.Effect>
<Grid Width="200" Height="200" MouseDown="Grid_MouseDown_1" Background="Red"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="WindowState" Value="Maximized">
<Setter Property="Effect" TargetName="brd" Value="{x:Null}"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Template>

Delegating the clicked event to the templated control

Since the CheckBox control doesn't increase the checkbox when the font size is increased, I decided to create my own variation of it (since it's to be used on a touch screen).
I have the following template:
<ControlTemplate x:Key="YesNoCheckbox" TargetType="{x:Type CheckBox}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Width="100" Name="myButton"/>
<ContentPresenter Grid.Column="1" Margin="4,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Left" RecognizesAccessKey="True"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" TargetName="myButton" Value="Ja"/>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Content" TargetName="myButton" Value="Nei"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
However, I need to delegate the click event from the button to the checkbox, so that the checked state is checked/unchecked.
I'm a total newbie, and this is probably very easy, but I fail to search this information up, probably due to a lack of correct keywords to search for.
I'm looking forward to hearing from you.
Thanks,
Stefan
CheckBox is a ToggleButton by itself and you are trying to place another button inside, which is wrong.
CheckBox changes its IsChecked automatically when it gets clicked. So you don't need to define another button inside. What you need to do instead is define a visual drawing in the CheckBox template that will scale when the FontSize increases and that will reflect current CheckBox state. Basically, you need to modify default style.

Styling a WPF layout grid background (of each cell, row, column)

I would like to know if there is any way to style a WPF layout grid's cells, rows and columns. I've been trying to find any information and the few mentions I've found have not been that informative.
I would like to style the grid to look like the one in the linked screenshot.
If the actual control does not support it, can I inherit it somehow and do it then? I am quite new to WPF so any help would be very appreciated.
One other thing, I know I can style each and every control within the grid, but it seems like overkill. I would like to have a grid that does it itself.
screenshot http://img21.imageshack.us/img21/2842/capturehz8.png
#Dan recommends WPF Unleashed, which I'm currently reading. Just this morning, I come across a section addressing your question.
Chapter 6, Page 161:
FAQ: How can I give Grid cells background colors, padding, and borders like I can with cells of a HTML Table?
There is no intrinsic mechanism to give Grid cells such properties, but you can simulate them pretty easily thanks to the fact that multiple elements can appear in any Grid cell. To give a cell a background color, you can simply plop in a Rectangle with the appropriate Fill, which stretches to fill the cell by default. To give a cell padding, you can use auto sizing and set the Margin on the appropriate child element. For borders, you can again use a Rectangle but give it an explicit Stroke of the appropriate color, or you can simply use a Border element instead.
Just be sure to add such Rectangles or Borders to the Grid before any of the other children (or explicitly mark them with the ZIndex attached property), so their Z order puts them behind the main content.
Btw, WPF Unleashed rocks. Its very well written, and the print in full color makes it even more easier to read.
Here's a quick (very rough sample) that you could hack around to get the format you want (if you're serious about working with WPF, you'll find Blend an enormous help in getting your layouts looking good):
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style x:Key="CustomerDefinition" TargetType="TextBlock">
<Setter Property="Control.FontFamily" Value="Tahoma"/>
<Setter Property="Control.FontSize" Value="12"/>
<Setter Property="Control.Foreground" Value="Red"/>
</Style>
<Style TargetType="{x:Type Label}">
<Setter Property="Width" Value="100"/>
</Style>
<Style x:Key="{x:Type TextBox}" TargetType="{x:Type TextBox}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="MinWidth" Value="120"/>
<Setter Property="MinHeight" Value="20"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="Width" Value="200"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBoxBase}">
<Border
Name="Border"
Background="#FFEBE9E9"
BorderBrush="#FF8B8787"
BorderThickness="1"
CornerRadius="2"
Padding="3">
<ScrollViewer x:Name="PART_ContentHost" Margin="0"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Border" Property="Background"
Value="#EEEEEE"/>
<Setter TargetName="Border" Property="BorderBrush"
Value="#EEEEEE"/>
<Setter Property="Foreground" Value="#888888"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<LinearGradientBrush x:Key="NormalBrush" StartPoint="0,0" EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Offset="0.0" Color="#FFF0EDED"/>
<GradientStop Offset="1.0" Color="#FFE1E0E0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
</Page.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="26"/>
<RowDefinition Height="23"/>
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
</Grid.RowDefinitions>
<TextBlock
Grid.ColumnSpan="2"
Grid.Row="0"
Style="{StaticResource CustomerDefinition}"
Text="Customer Definition"/>
<Border
Grid.Column="0"
Grid.Row="1"
Background="#FFEBE9E9"
BorderBrush="#FF8B8787"
BorderThickness="1">
<StackPanel Background="{StaticResource NormalBrush}" Orientation="Horizontal">
<Label Content="Customer Code"/>
<TextBox Text="SMITHA 098 (normally I'd bind here)"/>
</StackPanel>
</Border>
<Border
Grid.Column="1"
Grid.Row="1"
Background="#FFEBE9E9"
BorderBrush="#FF8B8787"
BorderThickness="1">
<StackPanel Background="{StaticResource NormalBrush}" Orientation="Horizontal">
<Label Content="Customer Type"/>
<TextBox Text="PRIVATE INDIVIDUAL"/>
</StackPanel>
</Border>
</Grid> </Page>
The WPF Grid doesn't have visible cells as such. Think of them as invisible grid lines against which you can have child elements be aligned.
So, to style the grid's cells, you have to style the items that are aligned inside the grid.
It is confusing to think of the Grid as being anything like a WinForms DataGrid. I guess its closest WinForms equivalent is the TableLayout control.
Check out some 3rd party grid controls. I used the DevExpress one while it was in beta and found it pretty straightforward.
I would recommend using borders for your styling.
You could recreate that layout pretty easily by creating borders for each row and each column and set the rowspans and colspans accordingly.
You will have 5 borders with colspan 2, these borders will take care of your gradient backgrounds for each row and the borders along the top and bottom of each row. Then you will have 2 borders with rowspan 5 these will handle the column borders. Imagine that you are overlaying the borders to form the visual grid effect you are after.
For the header and outer border, just wrap the entire grid with a border and style as needed.
I would recommend storing your styles as resources so you can keep all your styling info in one place.
Take care to learn how the styling works because it is pretty powerful, but there is a learning curve as it is quite different to the way CSS works. I would recommend reading WPF Unleashed if you can.
I found this post when looking for method for setting margin (or padding) for DataGrid cells. My problem was solved thanks to example xaml code posted at (near the end) -- pretty minimalistic.
http://forums.silverlight.net/forums/p/16842/55997.aspx

Resources