WPF multi-line TabControl without rearranging rows - wpf

The WPF TabControl with its default TabPanel arranges tab items into multiple rows when the horizontal size is too small. Then the tab selection changes the order of these rows, so the selected tab item is always in the first row.
I found several articles on how to replace TabPanel with another items control so instead of the multiline behavior they get scrolling tabs.
I would like to keep the multiple rows (no scrolling), but disable the rearrangement of rows. Once the tabs are created, they should stay in position, no matter how the selection changes. Is this possible?

have you tried overriding the default style with something like this? ie: using a wrappanel instead of a TabPanel?
<Style x:Key="{x:Type TabControl}" TargetType="{x:Type TabControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid TabNavigation="Local" SnapsToDevicePixels="true" ClipToBounds="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Name="ColumnDefinition0" />
<ColumnDefinition Name="ColumnDefinition1" Width="0" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Name="RowDefinition0" Height="Auto" />
<RowDefinition Name="RowDefinition1" Height="*" />
</Grid.RowDefinitions>
<WrapPanel Name="HeaderPanel" ZIndex="1" TabIndex="1" Column="0" Row="0" Margin="2,2,2,0" IsItemsHost="true" />
<Border Name="ContentPanel" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" TabNavigation="Local" DirectionalNavigation="Contained" TabIndex="2" Column="0" Row="1">
<ContentPresenter Name="PART_SelectedContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Margin="{TemplateBinding Padding}" ContentSource="SelectedContent" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

The only solution I found was modifying the framework's TabPanel class so that its int GetActiveRow(int[] solution) method always returns 0. Although this solves the problem, I'm not sure it is legal to use the framework's source in this way.

I think vigoo's answer is incomplete. int GetActiveRow(int[] solution) is a private method for the TabPanel class inside of the System.Windows.Controls.Primitives namespace. So you must create a copy of the TabPanel class from the WPF source code (which is available at WPF source github and place it in a new namespace.
// Returns the row which contain the child with IsSelected==true
private int GetActiveRow(int[] solution)
{
// Prevent Tabs from re-ordering when selecting a tab
return 0;
/*int activeRow = 0;
int childIndex = 0;
if (solution.Length > 0)
{
foreach (UIElement child in InternalChildren)
{
if (child.Visibility == Visibility.Collapsed)
continue;
bool isActiveTab = (bool)child.GetValue(Selector.IsSelectedProperty);
if (isActiveTab)
{
return activeRow;
}
if (activeRow < solution.Length && solution[activeRow] == childIndex)
{
activeRow++;
}
childIndex++;
}
}
// If the is no selected element and aligment is Top - then the active row is the last row
if (TabStripPlacement == Dock.Top)
{
activeRow = _numRows - 1;
}
return activeRow;*/
}
Then create a template for the TabControl and reference your new TabPanel in the Template (I called it overrides:TabPanel here). Don't forget to add a reference to the new TabPanel class in your ResourceDictionary (or wherever you defined the new style) xmlns:overrides="clr-namespace:MyNamespace.WPF.Overrides".
<Style x:Key="staticTabs"
TargetType="{x:Type TabControl}">
<Setter Property="Padding" Value="2" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Background" Value="{StaticResource TabItem.Selected.Background}" />
<Setter Property="BorderBrush" Value="{StaticResource TabItem.Selected.Border}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid x:Name="templateRoot"
ClipToBounds="true"
KeyboardNavigation.TabNavigation="Local"
SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="ColumnDefinition0" />
<ColumnDefinition x:Name="ColumnDefinition1"
Width="0" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition x:Name="RowDefinition0"
Height="Auto" />
<RowDefinition x:Name="RowDefinition1"
Height="*" />
</Grid.RowDefinitions>
<overrides:TabPanel x:Name="headerPanel"
Grid.Row="0"
Grid.Column="0"
Margin="2,2,2,0"
Panel.ZIndex="1"
Background="Transparent"
Grid.IsSharedSizeScope="True"
IsItemsHost="true"
KeyboardNavigation.TabIndex="1" />
<Border x:Name="contentPanel"
Grid.Row="1"
Grid.Column="0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabIndex="2"
KeyboardNavigation.TabNavigation="Local">
<ContentPresenter x:Name="PART_SelectedContentHost"
Margin="{TemplateBinding Padding}"
ContentSource="SelectedContent"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Finally your Tab control will reference this new Template:
<TabControl Style="{StaticResource staticTabs}" />

Related

How do I remove the Mouse Over Effect on WPF Ribbon Group?

I'm working on this Ribbon Control in WPF: System.Windows.Controls.Ribbon.Ribbon
I've made the background orange and I've changed the style slightly. It looks like this:
When I move the cursor over a group it looks like this:
I want to remove the white mouse over / hover effect, but I don't know which Style or Template I should look at. I've tried all these:
Ribbon
RibbonTab
RibbonTabHeader
RibbonButton
RibbonGroup
Is it possible? How do I do it?
So it is inside the style of the RibbonGroup:
<!--Ribbon Group - Style-->
<Style TargetType="RibbonGroup">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate x:Name="ribbonGroupControlTemplate" TargetType="RibbonGroup" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Border Background="{TemplateBinding Panel.Background}" Name="GroupBorder" Margin="1,2,0,0">
<Grid Name="MainGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" MinHeight="16" />
</Grid.RowDefinitions>
<Border BorderThickness="1,1,1,1" CornerRadius="2,2,2,2" BorderBrush="{TemplateBinding RibbonControlService.MouseOverBorderBrush}" Background="{TemplateBinding RibbonControlService.MouseOverBackground}" Name="PART_HotBackground" Opacity="0" SnapsToDevicePixels="True" Grid.RowSpan="3" />
<Border Background="{TemplateBinding Border.BorderBrush}" Name="SeparatorBorder" Width="1" Height="75" VerticalAlignment="Center" SnapsToDevicePixels="True" Grid.Column="1" Grid.RowSpan="3" />
<Border Padding="3,0,3,0" Margin="2,1,2,0">
<Grid>
<ItemsPresenter Name="ItemsPresenter" />
<ContentControl Name="PART_TemplateContentControl" Visibility="Collapsed" Focusable="False" />
</Grid>
...
The culprit is this Border:
<Border BorderThickness="1,1,1,1" CornerRadius="2,2,2,2" BorderBrush="{TemplateBinding RibbonControlService.MouseOverBorderBrush}" Background="{TemplateBinding RibbonControlService.MouseOverBackground}" Name="PART_HotBackground" Opacity="0" SnapsToDevicePixels="True" Grid.RowSpan="3" />
When I comment out or remove that border and the triggers that go with it; it solves my problem.

GridViewHeaderRowPresenter scrolls with custom TreeListView control

I have a custom TreeListView - based on the standard TreeView - with the following style:
<Style TargetType="{x:Type c:TreeListView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type: c:TreeListView}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<GridViewHeaderRowPresenter DockPanel.Dock="Top" Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}" />
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
As I have no ScrollViewer, the content will not scroll so if the content requires more space than is available, it looks like this:
So I added a ScrollViewer so that the style now Looks like this:
<Style TargetType="{x:Type c:TreeListView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type: c:TreeListView}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Focusable="False" Padding="{TemplateBinding Padding">
<DockPanel>
<GridViewHeaderRowPresenter DockPanel.Dock="Top" Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}" />
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</DockPanel>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
With the content expanded as in the first Image, the Content is not able to scroll, but the column Header scrools as well, which is not wished for :D
Could anyone please help me so that only the content gets scrolled, and not the columns bound to the GridViewHeaderRowPresenter?
Thank you!
[EDIT]
Thanks to the related links to the right, I found the answer here.
Instead of having the DockPanel within the ScrollViewer I needed only to have the ItemsPresenter inside the ScrollViewer. So with the following Style it now works.
<Style TargetType="{x:Type c:TreeListView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:TreeListView}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<GridViewHeaderRowPresenter DockPanel.Dock="Top" Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}" />
<ScrollViewer Focusable="False" Padding="{TemplateBinding Padding}">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ScrollViewer>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Many thanks to Nikhil Agrawal for his code!
The solution given is incomplete and will only work if you have a fixed width control. By only wrapping the ItemsPresenter the header columns will not scroll horizontally if needed. This results in shifted value columns which then mismatch the headers.
I've tried applying SelectiveScrollingGrid.SelectiveScrollingOrientation="Horizontal" to GridViewHeaderRowPresenter but haven't been successfull with that approach.
Instead, you could add a scroll viewer to the GridViewHeaderRowPresenter and to the ItemsPresenter and then synchronize them. It's not the best solution, but at least it works.
<Style TargetType="{x:Type local:TreeListView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TreeListView}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<ScrollViewer DockPanel.Dock="Top"
HorizontalScrollBarVisibility="Hidden"
Name="scrollViewerHeader"
VerticalScrollBarVisibility="Disabled">
<GridViewHeaderRowPresenter Columns="{StaticResource gvcc}"/>
</ScrollViewer>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
Name="scrollViewerBody"
VerticalScrollBarVisibility="Auto">
<ItemsPresenter />
</ScrollViewer>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
And the code behind:
public class TreeListView : TreeView
{
private ScrollViewer _scrollViewerHeader;
private ScrollViewer _scrollViewerBody;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_scrollViewerHeader = (ScrollViewer)Template.FindName("scrollViewerHeader", this);
_scrollViewerBody = (ScrollViewer)Template.FindName("scrollViewerBody", this);
_scrollViewerBody.ScrollChanged += (sender, e) =>
{
_scrollViewerHeader.Width = e.ViewportWidth;
_scrollViewerHeader.ScrollToHorizontalOffset(e.HorizontalOffset);
};
}
}
The way DataGrid solves it is it customizes the Template of the ScrollViewer to place the GridViewHeaderRowPresenter outside the ScrollViewer ScrollContentPresenter:
https://github.com/dotnet/wpf/blob/059ba38771aef10d2172a3ca0e247dfbfa8cefde/src/Microsoft.DotNet.Wpf/src/Themes/PresentationFramework.Aero2/Themes/Aero2.NormalColor.xaml#L987-L993
This way when the scrollbars scroll, they don't impact the header row.
The ListView that uses GridView does a similar thing here:
https://github.com/dotnet/wpf/blob/059ba38771aef10d2172a3ca0e247dfbfa8cefde/src/Microsoft.DotNet.Wpf/src/Themes/PresentationFramework.Aero2/Themes/Aero2.NormalColor.xaml#L2628
At the same time they wrap the header row in another ScrollViewer, and honestly I don't understand why.
Once I find out more I will expand on this answer. I think this approach if it works is cleaner than synchronizing scroll viewers.

WPF Flexible TabControl Header

I want to have a TabControl with multiple TabItems. These TabItems each have a header text. These texts may vary a lot in length (like 5 chars long and 15 chars long).
I want the TabControl to align the headers in one row only.
All tab headers should use the same width, and when there is enough space available, i want them the to use all the space available, up to a MaxWidth, that is the same for all items.
So if i want to use vMaxWidth` of 100 for 7 items, the tab header should be max 700 in width. If there is more space available, it should be ignored.
If there is less space available, i want that space to be distributed equally between the items. If the text gets cut off, i want to use TextWrapping.
I have tried multiple approaches to this problem now, this is my current setup:
<Style x:Key="Style-TabControl-Main" TargetType="{x:Type TabControl}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid KeyboardNavigation.TabNavigation="Local">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Border BorderThickness="0,0,0,1" Margin="13,0,0,0" BorderBrush="{StaticResource Brush-White}">
<StackPanel Panel.ZIndex="1" x:Name="HeaderPanel" IsItemsHost="True" KeyboardNavigation.TabIndex="1" Background="Transparent"
Orientation="Horizontal"/>
</Border>
<Border x:Name="Border"
Grid.Row="1" Grid.ColumnSpan="2"
KeyboardNavigation.TabNavigation="Local"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabIndex="2"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And the TabItem Style
<Style x:Key="Style-TabItem-Main" TargetType="{x:Type TabItem}">
<Setter Property="Height" Value="31"/>
<Setter Property="Width" Value="180" />
<Setter Property="Foreground" Value="{DynamicResource Brush-BrightRegular-Foreground}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border x:Name="Border" Cursor="Hand"
Margin="2,0,0,0"
BorderThickness="1,1,1,0"
CornerRadius="4,4,0,0"
BorderBrush="{DynamicResource Brush-BrightRegular-Background}"
Background="{DynamicResource Brush-White}">
<ContentPresenter x:Name="Content" VerticalAlignment="Center" HorizontalAlignment="Stretch" ContentSource="Header" RecognizesAccessKey="True"
TextBlock.TextAlignment="Center" TextBlock.FontSize="16" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="{DynamicResource Brush-White}"/>
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Brush-DefaultDark-Background}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I am using a StackPanel instead of a TabPanel to get rid of the "stacking", that occurs, when you resize a default TabControl. However, i cannot get the rest of my requirements to work. I tried applying a MaxWidth (instead of fixed width) to the TabItem headers, but that of course doesn't work, because the item than shrinks to its minimum required size.
Step 1 (first attempt): Put headers in a single row, and give each header the same width.
This can be achieved by using a UniformGrid instead of the standard TabPanel, and lock its row count to 1. Here is a stripped-down version of your TabControl style:
<Style x:Key="Style-TabControl-Main" TargetType="{x:Type TabControl}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border>
<UniformGrid x:Name="HeaderPanel" IsItemsHost="True"
Rows="1" />
</Border>
<Border x:Name="Border" Grid.Row="1"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Step 2: Restrict headers to a MaxWidth and apply text wrapping.
The MaxWidth can be set in the TabItem style, along with a HeaderTemplate which wraps text (you can still use your custom ControlTemplate here to style the TabItem parts):
<Style x:Key="Style-TabItem-Main" TargetType="{x:Type TabItem}">
<Setter Property="MaxWidth" Value="100" />
<!--https://social.msdn.microsoft.com/forums/vstudio/en-US/df4f7fc3-f0ec-4ed1-a022-a32650e49cb3/how-to-wrap-header-text-in-tabcontrol-->
<Setter Property="HeaderTemplate" >
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding}" TextWrapping="Wrap" />
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
...
</Setter>
</Style>
Troubleshooting: Now, if you apply the MaxWidth in Step 2, you'll probably want to left-align the UniformGrid when the TabControl gets too wide..
<UniformGrid x:Name="HeaderPanel" IsItemsHost="True"
Rows="1" HorizontalAlignment="Left" />
..but you don't want that when the MaxWidth hasn't been reached yet, and the items should stretch across the entire width of the TabControl (aka Step 1). So we need a way to switch that HorizontalAlignment depending on whether the items' MaxWidth (if set) has been reached.
Step 1 (revisited): Let's try to make our own UniformGrid:
public class UniformTabPanel : UniformGrid
{
public UniformTabPanel()
{
this.IsItemsHost = true;
this.Rows = 1;
//Default, so not really needed..
this.HorizontalAlignment = HorizontalAlignment.Stretch;
}
protected override Size MeasureOverride(Size constraint)
{
var totalMaxWidth = this.Children.OfType<TabItem>().Sum(tab => tab.MaxWidth);
if (!double.IsInfinity(totalMaxWidth))
{
this.HorizontalAlignment = (constraint.Width > totalMaxWidth)
? HorizontalAlignment.Left
: HorizontalAlignment.Stretch;
}
return base.MeasureOverride(constraint);
}
}
Now, we can replace the UniformGrid in our TabControl style this new panel:
...
<Border>
<mycontrols:UniformTabPanel x:Name="HeaderPanel" />
</Border>
...
...and the TabControl should function as expeced.
Sphinxx answer is correct, however i needed to add the following code to the UniformTabPanel, to make it work like i want (resize the headers to maxwidth when enough space is available)
I added the following code to the UniformTabPanel, and it now does what i need:
protected override Size MeasureOverride(Size constraint)
{
var children = this.Children.OfType<TabItem>();
var totalMaxWidth = children.Sum(tab => tab.MaxWidth);
if (!double.IsInfinity(totalMaxWidth))
{
this.HorizontalAlignment = (constraint.Width > totalMaxWidth)
? HorizontalAlignment.Left
: HorizontalAlignment.Stretch;
foreach (var child in children)
{
child.Width = this.HorizontalAlignment == System.Windows.HorizontalAlignment.Left
? child.MaxWidth
: Double.NaN;
}
}
return base.MeasureOverride(constraint);
}

How to specify 3/4th of are for one content presenter?

I am creating a new template for Tab control
in which I need to arrange items like attached image. The style given below is to have main tabs ..and contents... While mentioning the content (content presenter) I have to specify the grid column/row...So if I use row column/row as "0","0"..then all my contents will in the left top area...
Please tell me how do I specify a content presenter with 3/4 area of the grid.
<Style x:Key="OutlookTabControlStyle" TargetType="{x:Type TabControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid ClipToBounds="true" SnapsToDevicePixels="true"
KeyboardNavigation.TabNavigation="Local">
<Grid.RowDefinitions>
<RowDefinition x:Name="RowDefinition0" Height="Auto"/>
<RowDefinition x:Name="RowDefinition1" Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="ColumnDefinition0"/>
<ColumnDefinition x:Name="ColumnDefinition1" Width="0"/>
</Grid.ColumnDefinitions>
<Grid x:Name="ContentPanel" Grid.Column="0" Grid.Row="1">
<ContentPresenter SnapsToDevicePixels=
"{TemplateBinding SnapsToDevicePixels}" Margin="2,2,2,2"
x:Name="PART_SelectedContentHost"
ContentSource="SelectedContent"/>
</Grid>
<StackPanel HorizontalAlignment="Stretch" Margin="0,-2,0,0"
x:Name="HeaderPanel" VerticalAlignment="Bottom" Width="Auto"
Height="Auto" Grid.Row="1" IsItemsHost="True"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground"
Value="{DynamicResource
{x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My question is how do I allocate the remaining part (other than main tabs area) as content paresenter... I can see Canvas as one option. please help me if you know more about this.
Create a Grid with two Columns: one column with a width of * and one with a width of 3*. This will make make your 2nd column 3 times the size of the first column, or 3/4 of the total size
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnsDefinitions>
</Grid>
As an alternative you don't want to use a Grid, I usually use a MathConverter which allows me to adjust a bound value by a mathematical formula. The code for the MathConverter can be found here
<Grid Canvas.Left="{Binding ElementName=ParentPanel, Path=ActualWidth,
Converter={StaticResource MyMathConverter},
ConverterParameter=#VALUE*.75}" />

How to disable drag thumb on a ScrollBar in WPF?

Two question:
How to disable drag thumb on a ScrollBar in WPF?
Is there a way to limit drag thumb position? like LargeChange or SmallChange?
Edit the template of the ScrollBar control and set the IsEnabled property of the Thumb to false
Not sure you can do that directly from code or XAML but you might add 2 new DP to a control which inherits the ScrollBar class and then change the template to have this new feature
Maybe it will be usefull for someone
If you wanna disable drag thumb there two way:
Rewrite controll for your scrollbar
In controll template do not rewrite thumb, just use rectangle or just leave it empty
Example:
<Style TargetType="{x:Type ScrollBar}">
<Setter Property="Background" Value="{StaticResource BackgroundColor}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ScrollBar}">
<Grid x:Name="Bg" SnapsToDevicePixels="true">
<Grid.RowDefinitions>
<RowDefinition MaxHeight="{DynamicResource {x:Static SystemParameters.VerticalScrollBarButtonHeightKey}}"/>
<RowDefinition Height="0.00001*"/>
<RowDefinition MaxHeight="{DynamicResource {x:Static SystemParameters.VerticalScrollBarButtonHeightKey}}"/>
</Grid.RowDefinitions>
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Row="1" />
<RepeatButton Grid.Row="0" Command="ScrollBar.PageUpCommand" Style="{StaticResource ScrollBarButton}" Content="M 0 4 L 8 4 L 4 0 Z"/>
<Rectangle Grid.Row="1" VerticalAlignment="Top" x:Name="ThumbReplacer" Fill="{DynamicResource ScrollColor}"/>
<RepeatButton Grid.Row="2" Command="ScrollBar.PageDownCommand" Style="{StaticResource ScrollBarButton}" Content="M 0 0 L 4 4 L 8 0 Z"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Resources