WPF negative margins are not displayed correctly - wpf

I am trying to simulate a tab control with a nice TabHeader and TabContent. The control should look something like this:
This is achieved by setting the `Margin' of the first Header - "HOME" to Margin="2 0 2 -1".
ISSUE: if I re-size the window to a certain smaller width, the header item visually clips its content. Here is the result:
I really would like to know why this is happening and how ca i avoid this.
The sample xaml to prove the problem:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="550" Width="525">
<Grid Margin="0 50">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border BorderThickness="1" BorderBrush="Black" Grid.Row="1"/>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<Border Width="50" Margin="2 0 2 -1" BorderThickness="1 1 1 0" BorderBrush="Black" Background="White">
<TextBlock Text="HOME" />
</Border>
<Border Width="150" Margin="2 -20" Height="20" BorderThickness="1 1 1 0" >
<TextBlock Text="EDIT" />
</Border>
</StackPanel>
</Grid>

When you resize the window, XAML renderer redraw any flexible (still can be resized or moved relatively). when you reach the StackPanel width limit (limited by what it contains or a fixed Width) the control is ignored at redrawing (even what it contains) and the renderer keeps redrawing other flexible controls; in your case : the first border. that's why comes suddenly on the top of the others.
Moving the margin to the StackPanel will do the trick :
<StackPanel Orientation="Horizontal" Grid.Row="0" Margin="2 0 2 -1">
<Border Width="50" BorderThickness="1 1 1 0" BorderBrush="Black" Background="White">
<TextBlock Text="HOME" />
</Border>
<Border Width="150" Height="20" BorderThickness="1 1 1 0" >
<TextBlock Text="EDIT" />
</Border>
</StackPanel>

Here is a solution with 2 columns
<Grid Margin="0,50">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.ColumnSpan="2" BorderThickness="1" BorderBrush="Black" Grid.Row="1" />
<StackPanel Orientation="Horizontal" Grid.Row="0">
<Border Width="50" Margin="2,0,2,-1" BorderThickness="1,1,1,0" BorderBrush="Black" Background="White">
<TextBlock Text="HOME" />
</Border>
<Border Width="50" Margin="2" Height="20" BorderThickness="1,1,1,0" >
<TextBlock Text="EDIT" />
</Border>
</StackPanel>
</Grid>
This makes the grid not get clipped by the window size.
EDIT added additional column to push the border to the edge.
Cheers,
Eric

I would theorize that this is an oddity in the layout code of StackPanel. You should be able to work around it by moving the negative margin to the StackPanel itself, as opposed to the tabs within:
<Grid Margin="0 50" UseLayoutRounding="True">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border BorderThickness="1" BorderBrush="#7FFF0000" Grid.Row="1" />
<StackPanel Orientation="Horizontal" Grid.Row="0" Margin="0,0,0,-1">
<Border Width="50" Margin="2 0 2 0" BorderThickness="1 1 1 0" BorderBrush="Lime" Background="Yellow">
<TextBlock Text="HOME" />
</Border>
<Border Width="150" Margin="2 0 2 0" Height="20" BorderBrush="Blue" BorderThickness="1 1 1 0" Background="Yellow">
<TextBlock Text="EDIT" />
</Border>
</StackPanel>
</Grid>
Note that I changed the colors to assist with visual debugging. It's a useful technique, but you'll probably want to change them back :).

Related

ScrollViewer not working on user control wpf

I have a User Control 1 and a Page 1, what I'm trying to do is adding a scrollViewer to the User Control 1, but it does not working.
I would like to know what's wrong in this case?
Page 1 :
<Border BorderThickness="0" x:Name="border1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"></ColumnDefinition>
<ColumnDefinition Width="1.5*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="325"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" HorizontalScrollBarVisibility="Visible" Width="auto" Grid.Column="0">
<local1:UserControl1 Grid.Row="0" Margin="10,10,10,10" Grid.Column="0" ScrollViewer.HorizontalScrollBarVisibility="Visible" ></local1:UserControl1>
</ScrollViewer>
</Grid>
</Border>
User Control 1:
<Grid Background="White">
<Border x:Name="border1" BorderBrush="Black" BorderThickness="1" >
<TabControl
Background="{StaticResource WhiteBruch}"
x:Name="TabControl1"
Height="auto"
BorderThickness="0" >
<TabItem x:Name="Tab1"
Style="{StaticResource TabItemStyle}">
<TabItem.Header>
<TextBlock Style="{StaticResource TitleText}"
Text="{StaticResource Text1}"
Name="TabItem1"
/>
</TabItem.Header>
</TabItem>
</TabControl>
</Border>
</Grid>

Negative-margin control get clipped when resizing the window in WPF

I try to understand why a border element get clipped when reducing the width of the main window.
Please take a look the code block below.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="500" Name="MainWin">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Border Background="Blue" Grid.Row="0" BorderBrush="Black" Width="{Binding ElementName=MainWin, Path=Width}" />
<Grid Grid.Row="1" Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="150" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Background="Black">
<Border Background="White" Width="150" Height="150" BorderBrush="Black" BorderThickness="2"
Margin="0,-100,0,0">
<TextBlock Text="{Binding ElementName=MainWin, Path=Width}" FontSize="14" FontWeight="Bold"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
</StackPanel>
<StackPanel Grid.Column="1" Background="Red" />
<StackPanel Grid.Column="2" Background="Yellow" />
</Grid>
</Grid>
</Window>
Here is what the border appears in the original window width:
Non-resized window
As you can see that the border is displayed outside its container because of the negative top margin, -100 in this case. This is what I expect to have for the border. But when I reduce the main window width to reach the right edge of the red rectangle the outside part of the border get clipped.
Resized window
I have tried to place this border element inside a custom StackPanel which overrides ArrangeOverride, MeasureOverride and GetLayoutClip method but unfortunately these methods are not invoked when the main window is being resized.
I appreciate if somebody can explain me what the reason is and how to work around with this issue.
Thanks a lot.
Based on the explanation of #Marks, here is my solution
Create a custom grid and overrides MeasureOverride method
Replace the inner grid by this custom grid
CustomGrid class
public class CustomGrid : Grid
{
private double _originalHeight = 0;
protected override Size MeasureOverride(Size constraint)
{
Size? size = null;
if (constraint.Width <= 300)
{
size = new Size(constraint.Width, _originalHeight);
}
else
{
size = base.MeasureOverride(constraint);
_originalHeight = constraint.Height;
}
return size.Value;
}
}
XAML code
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="300" Width="500" Name="MainWin">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Border Background="Blue" Grid.Row="0" BorderBrush="Black" Width="{Binding ElementName=MainWin, Path=Width}" />
<wpfApplication1:CustomGrid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="150" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Background="Black">
<Border Background="White" Width="150" Height="150" BorderBrush="Black" BorderThickness="2"
Margin="0,-100,0,0">
<TextBlock Text="{Binding ElementName=MainWin, Path=Width}" FontSize="14" FontWeight="Bold"
HorizontalAlignment="Center"
VerticalAlignment="Bottom" />
</Border>
</StackPanel>
<StackPanel Grid.Column="1" Background="Red" />
<StackPanel Grid.Column="2" Background="Yellow" />
</wpfApplication1:CustomGrid>
</Grid>
You're binding the blue border's Width to the MainWindow's Width.
For the future: if you want to bind to the width of any FrameworkElement bind to its ActualWidth property.
The order in which WPF draws its stuff is quite dependent on the containing control. I'd say in your case the outer Grid draws its children that need updating in the order they are defined. So you're good to go as long as the inner grid changes along with the border. This is the case as long as the Width of the third column changes. Once it's at 0 there's no more change so it doesn't get updated.
(2) is speculation =)
don't do (1), there's no need for it
Use one Grid
Some XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="150" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Background="Blue" BorderBrush="Black" Grid.ColumnSpan="3"/>
<StackPanel Grid.Column="0" Grid.Row="1" Background="Black" >
<Border Background="White" Width="150" Height="150" BorderBrush="Black" BorderThickness="2" Margin="0,-100,0,0">
<TextBlock Text="{Binding ElementName=MainWin, Path=ActualWidth}" FontSize="14" FontWeight="Bold"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
</StackPanel>
<StackPanel Grid.Column="1" Grid.Row="1" Background="Red" />
<StackPanel Grid.Column="2" Grid.Row="1" Background="Yellow" />
</Grid>

Display a VisualBrush from a hidden control

I got the following xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock x:Name="_sampleText"
Grid.Row="0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Width="200"
Height="50"
FontSize="36"
Text="Hello world"
TextAlignment="Center"
Visibility="Collapsed" />
<Border Grid.Row="1"
Width="{Binding ActualWidth, ElementName=_sampleText}"
Height="{Binding ActualHeight, ElementName=_sampleText}">
<Border.Background>
<VisualBrush Stretch="None"
Visual="{Binding ElementName=_sampleText}" />
</Border.Background>
</Border>
</Grid>
I want my visual to be render even if the control he bind too is not visible. Is there a way of doing that?
You could also place the TextBlock in a Border and make that Hidden:
<Border Visibility="Hidden">
<TextBlock ... />
</Border>
No. If the Visibility=Collapsed then it won't take part in the measure/arrange layout pass and not be rendered (the Width and Height will be 0).
What effect are you trying to achieve? It looks like you want a preview pane? Reply in the comments and we can figure out the best approach.
Edit: There is a way to call the measure/update on a UIElement from code-behind however I think the best-approach for you would be use a BoolToVisibilityConverter.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<CheckBox x:Name="OptionCheckBox" Grid.Row="0" />
<Border Grid.Row="1" Visibility="{Binding Path=IsChecked, ElementName=OptionCheckBox", Converter={StaticResouce BoolToVisibilityConverter}}">
<TextBlock Width="200" Height="50" FontSize="36" Text="Hello world" TextAlignment="Center"/>
</Border>
</Grid>

Getting a WPF control to stretch vertically

I can't get my WPF layout working. I want the ListBox to stretch vertically by anchoring to the bottom of the window. It currently just sizes to the height of the controls in the StackPanel (the Add and Remove buttons), and resizes to accomodate items that are added. In WinForms I would just set ListView.Anchor to Top|Left|Bottom|Right but I shouldn't live in the past. I've tried a number of things like putting it in a DockPanel, wrapping everything in Canvas, etc., but nothing seems to be affected.
Here is my XAML:
<Window x:Class="FileDropAdmin.ViewsTestListView"
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:shared="http://schemas.markpad.net/winfx/xaml/shared"
Title="ViewsTestListView" Height="300" Width="416">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top" Text="Things:" />
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<ListBox x:Name="Things" DisplayMemberPath="ThingName" SelectedItem="CurrentThing" Grid.Column="0"/>
<StackPanel Margin="5 0 0 0" VerticalAlignment="Top" Grid.Column="1">
<Button x:Name="AddThing" Content="Add" Margin="0 0 0 0" VerticalAlignment="Top"/>
<Button x:Name="RemoveThing" Content="Remove" Margin="0 5 0 0" VerticalAlignment="Top"/>
</StackPanel>
</Grid>
</Grid>
</Window>
Set your second row to Height="*" and It should take all the space of the window if it's what you need
Add another row that takes up all the remainder space...
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
...then adjust the row values for the text block and list box...
<TextBlock Grid.Row="1" TextWrapping="Wrap" VerticalAlignment="Top" Text="Things:" />
<Grid Grid.Row="2">
You need following options...
You second row defintion needs to be Height="*"
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
and your ListBox needs ColumnSpan="2"
<ListBox x:Name="Things"
DisplayMemberPath="ThingName"
SelectedItem="CurrentThing"
Grid.ColumnSpan="2"
Grid.Column="0"/>
Also you said you used DockPanel... with dockpanel its even easier. All you have to set it LastChildFill="True" and add your ListBox as the last child in the dockpanel.
<DockPanel LastChildFill="True">
<Grid DockPanel.Dock="Top" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<StackPanel Margin="5 0 0 0" Grid.Column="0"
HorizontalAlignment="Stretch">
<TextBlock Text="Things:"/>
<TextBox HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
</StackPanel>
<StackPanel Margin="5 0 0 0" Grid.Column="1">
<Button x:Name="AddThing" Content="Add" Margin="0 0 0 0"/>
<Button x:Name="RemoveThing" Content="Remove"
Margin="0 5 0 0"/>
</StackPanel>
</Grid>
<ListBox x:Name="Things" DisplayMemberPath="ThingName"
SelectedItem="CurrentThing" />
</DockPanel>

Templates and inheritance

I have a big problem.
I use additional controls for Wpf. One of them is Telerik RadWindow
This control is already templated.
Now I want to create custom Window with will inherit from RadWindow, and make custom template, eg. One base window will contains grid and two buttons, second base window will contain two grids (master - detail).
The problem is that templates do not support inheritance. Perhaps is another way to template only the content of Winodow?
My code, that doesn't work (empty window appears, so template doesn't apply)
<Style TargetType="{x:Type local:TBaseRjWindow}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TBaseRjContent}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid Name="mGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition MaxHeight="40" MinHeight="30" />
<RowDefinition MaxHeight="40" MinHeight="30" />
<RowDefinition Height="Auto" />
<RowDefinition MaxHeight="40" MinHeight="30" />
</Grid.RowDefinitions>
<telerik:RadGridView Margin="10,10,10,10" Name="grid" Grid.Row="0" Grid.Column="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" ScrollMode="Deferred" AutoGenerateColumns="False" Width="Auto" >
</telerik:RadGridView>
<telerik:RadDataPager Grid.Row="1" Grid.Column="0"
x:Name="radDataPager"
PageSize="50"
AutoEllipsisMode="None"
DisplayMode="First, Previous, Next, Text"
Margin="10,0,10,0"/>
<StackPanel Grid.Row="1" Grid.Column="0" Margin="5 5 5 5" HorizontalAlignment="Left" Orientation="Horizontal" Height="20" Width="Auto" VerticalAlignment="Center" >
<telerik:RadButton x:Name="btAdd" Margin="5 0 5 0" Content="Dodaj" />
<telerik:RadButton x:Name="btEdit" Margin="5 0 5 0" Content="Edytuj" />
<telerik:RadButton x:Name="btDelete" Margin="5 0 5 0" Content="Usun" />
</StackPanel>
<StackPanel Name="addFields" Background="LightGray" Visibility="Collapsed" VerticalAlignment="Top" Grid.Row="2" Grid.Column="0" Width="Auto" Height="Auto" Orientation="Horizontal">
<GroupBox Header="Szczegoly" Margin="2 2 2 2" >
<Grid VerticalAlignment="Top" DataContext="{Binding SelectedItem, ElementName=grid}" Name="_gAddFields" Margin="0 0 0 0" Width="Auto" Height="Auto" >
</Grid>
</GroupBox>
</StackPanel>
<StackPanel Grid.Row="3" Grid.Column="0" Margin="5 5 5 5" HorizontalAlignment="Right" Orientation="Horizontal" Height="25" Width="Auto" VerticalAlignment="Center" >
<telerik:RadButton x:Name="btSave" IsDefault="True" Width="60" Margin="5 0 5 0" Content="Zapisz" />
<telerik:RadButton x:Name="btOK" IsDefault="True" Width="60" Margin="5 0 5 0" Content="Akceptuj" />
<telerik:RadButton x:Name="btCancel" IsCancel="True" Width="60" Margin="5 0 5 0" Content="Anuluj" />
</StackPanel>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Please help
You could use the ContentTemplate for the Window. It is a DataTemplate that will be used to display the Content set on the Window.

Resources