How to wrap TextBlock content in TreeView? - c#

I have TreeView, which displays some data using data templates. Here's XAML:
<TreeView Grid.Row="0" ItemsSource="{Binding Railways}" x:Name="tvDatawareObjects"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<!-- other templates here... -->
<HierarchicalDataTemplate DataType="{x:Type viewModels:ProjectViewModel}" ItemsSource="{Binding Phases}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Model.Code}" FontWeight="DemiBold" />
<TextBlock Text="{Binding Model.Title}" TextWrapping="Wrap" Foreground="Gray" Grid.Row="1" />
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type viewModels:CollectionViewModel}" ItemsSource="{Binding Items}">
<TextBlock Text="{Binding CollectionName}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Text wrapping for <TextBlock Text="{Binding Model.Title}" TextWrapping="Wrap" Foreground="Gray" Grid.Row="1" /> doesn't work. What am I doing wrong?

I believe the TextBlock isn't wrapping because it doesn't have a defined width. The grid column that the TextBlock is in has a * width which will grow as the TextBlock grows in width. Try setting a width on the TextBlock or the column and see if the change causes the TextBlock to wrap.
Update:
To be more specific, the problem is that the TreeViewItem will size itself to the size of its contents, the ColumnDefinition will fill the (infinitely) available space and the TextBlock, with no width restriction, will never wrap. This post does a good job of describing how the TreeViewItem behaves. To sum it up: the content area of the TreeViewItem is set to 'Auto' so it will grow to fit the contents. To explicitly set the width of the TreeViewItem try binding your ColumnDefinition width to the TreeView's ActualWidth.
XAML:
<TreeView Width="100">
<TreeViewItem>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding RelativeSource={RelativeSource AncestorType=TreeView}, Path=ActualWidth}"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="Lorem Ipsum" />
<TextBlock Text="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."
TextWrapping="Wrap" Grid.Row="1"/>
</Grid>
</TreeViewItem>
</TreeView>

I did it. :)
Accordind to the link, provided in the Dan's answer, the reason is lying in the default TreeViewItemTemplate.
Unfortunately, simple binding to TreeView.ActualWidth can't help. This is because width of each item is less than TreeView.ActualWidth by definition - items are rendered with some horizontal offset, depending on their level.
Hence, to solve the problem, I need to calculate width of item like this:
width = actual_width_of_tree_view - relative_horizontal_offset_of_item
To be more precise, I need ScrollViewer.ViewportWidth, so as TreeViewContent might be scrolled vertically, and visible area of TreeView will be less than TreeView.ActualWidth in this case.
Here's attached property:
public static Double GetProjectTitleWidth(DependencyObject obj)
{
return (Double)obj.GetValue(ProjectTitleWidthProperty);
}
public static void SetProjectTitleWidth(DependencyObject obj, Double value)
{
obj.SetValue(ProjectTitleWidthProperty, value);
}
public static readonly DependencyProperty ProjectTitleWidthProperty = DependencyProperty.RegisterAttached(
"ProjectTitleWidth",
typeof(Double),
typeof(DatawareSearchView),
new UIPropertyMetadata(0.0, ProjectTitleWidthChanged));
private static void ProjectTitleWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var targetElement = d as FrameworkElement;
if (targetElement != null)
{
var bindingExpr = targetElement.GetBindingExpression(ProjectTitleWidthProperty);
var sourceElement = bindingExpr.DataItem as FrameworkElement;
if (sourceElement != null)
{
// calculating relative offset
var leftTop = targetElement.TranslatePoint(new Point(0.0, 0.0), sourceElement);
// trying to find ScrollViewer
var border = VisualTreeHelper.GetChild(sourceElement, 0);
if (border != null)
{
var scrollViewer = VisualTreeHelper.GetChild(border, 0) as ScrollViewer;
if (scrollViewer != null)
{
// setting width of target element
targetElement.Width = scrollViewer.ViewportWidth - leftTop.X;
}
}
}
}
}
...and markup:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Model.Code}" FontWeight="DemiBold" />
<TextBlock Text="{Binding Model.Title}" TextWrapping="Wrap" Foreground="Gray" x:Name="tbTitle" Grid.Row="1"
localviews:DatawareSearchView.ProjectTitleWidth="{Binding RelativeSource={RelativeSource AncestorType=TreeView}, Path=ActualWidth}"/>
</Grid>
Of course, provided solution isn't universal - it assumes, that TreeView has Border and ScrollViewer.

try this
<TextBlock Text="{Binding Model.Title}" Width="{Binding ActualWidth,
ElementName=tvDatawareObjects}" TextWrapping="Wrap" Foreground="Gray" Grid.Row="1"/>

Related

Show a custom tooltip for a group of rectangles (points on a graph)

I have a Canvas on which I have drawn a number of Rectangles which represent a number of user selected positions on the canvas.
I want to create a ToolTip for each of the rectangles that shows the x and y coords of the rectangle and also the distance to another point: the "stylus point".
The x and y coords are known when a rectangle is created of course but the distance to the stylus point is not therefore the tooltip needs to update its text each time it is shown.
I've tried using a binding as below but this just puts the text "System.Windows.Control.ToolTip" in the tool tip.
...
Rectangle rectangle = new Rectangle
{
Width = _rectWidth,
Height = _rectWidth,
Fill = new SolidColorBrush(Colors.Red)
};
rectangle.ToolTip = new ToolTip();
Binding binding = new Binding()
{
Source = this,
Path = new PropertyPath("ToolTipBinding"),
Mode = BindingMode.OneWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
BindingOperations.SetBinding( rectangle.ToolTip as ToolTip ,ToolTipService.ToolTipProperty, binding);
}
public string ToolTipBinding
{
get
{
return "How would i get the data context here (even if it bound correctly)";
}
}
Any help greatly appreciated.
This is the solution I've come up with. this will show a list of points as squares on a canvas.TargetListbelow is a list of Target objects that contain the required data for each point. Hope this helps someone.
Resource created for the ToolTips:
<UserControl.Resources>
<Style TargetType="{x:Type ToolTip}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToolTip}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox Grid.Row="0" Grid.Column="0" Text="X:" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Path=PositionX}" />
<TextBox Grid.Row="1" Grid.Column="0" Text="Z:" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Path=PositionZ}" />
<TextBox Grid.Row="2" Grid.Column="0" Text="ΔX:" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Path=DeltaX}" />
<TextBox Grid.Row="3" Grid.Column="0" Text="ΔZ:" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Path=DeltaZ}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<UserControl.Resources>
Items control
<ItemsControl Grid.Row="0" Grid.Column="0" ClipToBounds="True" ItemsSource="{Binding TargetList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=PositionScreen.X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=PositionScreen.Z}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Width="{Binding RectangleSize}"
Height="{Binding RectangleSize}"
Fill="{Binding RectangleBrush}" >
<Rectangle.ToolTip>
<ToolTip />
</Rectangle.ToolTip>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Target class, showing the property names only. These are updated as required.
public class Target : ObservableObject
{
public Point3D PositionX...
public Point3D PositionZ...
public double DeltaX...
public double DeltaZ...
public Point3D PositionScreen...
public double RectangleSize...
public Brush RectangleBrush...
}

Adding multiple stack panels through C# code

I have a mom with multiple children. Each child has a name already assigned and needs user input for the value.
I am trying to generate a StackPanel with a TextBlock and TextBox for each child through code. The number of children are unknown so I need a loop to create each StackPanel and them to a "mom" StackPanel which is added to a Border control. When I run the code it runs through the loop fine but it does not show anything in the display.
Here is the code:
var momStackPanel = new StackPanel() { Orientation = Windows.UI.Xaml.Controls.Orientation.Vertical};
foreach (ChildItem item in ChildList)
{
var childItemSP = new StackPanel();childItemSP.Orientation = Orientation.Horizontal;
TextBlock nameTB = new TextBlock();
name.Text = item.Name;
childItemSP.Children.Add(nameTB);
TextBox valueTB = new TextBox();
valueTB .Text = item.Value;
childItemSP.Children.Add(valueTB);
}
BorderSection.Child = momStackPanel;
BorderSection.Visibility = Windows.UI.Xaml.Visibility.Visible;
Here is the XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="t4" Text="t4" Grid.Row="0"/>
<Border Grid.Row="1" HorizontalAlignment="Stretch">
<StackPanel >
<TextBlock x:Name="r1" Text="{Binding r1}" HorizontalAlignment="Center" />
<TextBlock x:Name="r2" Text="r2" Foreground="DarkGray"/>
</StackPanel>
</Border>
<Border x:Name="BorderSection" Grid.Row="2">
</Border>
<StackPanel x:Name="CommentPanel" Orientation="Vertical" Grid.Row="3" Margin="6,6,6,6">
<TextBox x:Name="Comment" Text="" MinHeight="100"/>
</StackPanel>
<StackPanel Orientation="Horizontal" >
<Button x:Name="Submit" Content="Submit" Click="Submit_Click_1"/>
<Button x:Name="Cancel" Content="Cancel" Click="Cancel_Click_1"/>
</StackPanel>
</Grid>
Any ideas how to deal with this?
The ItemsControl control acts like a StackPanel with a variable number of items. You can bind it to an ObservableCollection, so you don't have to mess with the view at all in the code-behind.
<ItemsControl ItemsSource="{Binding Children}">
<ItemsControl.Resources>
<ResourceDictionary Source="ChildTemplate.xaml"/>
</ItemsControl.Resources>
</ItemsControl>
Then you can provide the child's view in a separate file (ChildTemplate.xaml).
<DataTemplate DataType="{x:Type myNamespace:ChildViewModel}">
<StackPanel >
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
Don't forget you will have to call OnPropertyChanged for the ObservableCollection whenever you change it.

How do I make the Expander Header change along with the List item as typing in field occurs?

How do I make the Expander Header change along with the ListBox item as typing in field occurs?
* I just attempted to post this with an image, but this site does not allow me to post an image until I have a reputation greater than 10. The image is simply a window capture of the running WPF program (source below) where the [New] button was clicked once, "John Henry Doe" entered into the Name field and "123-4567" entered into the Phone field, then in bold red "1) As I type here" with arrows pointing to the Name and Phone fields, then in bold red "2) This changes" with arrow pointing to the "John Henry Doe: 123-4567" item in the ListBox, then in bold red "3) But this does not change" with an arrow pointing to "New Contact" of the Expander's Header. *
As can (if I was allowed to post the image) be seen in the image above, as the user types into the Name or Phone field, the ListBox Item changes. It changes because I do a .Refresh() on the KeyUp event. However, the Header of the Expander should be changing at the same time. There is no .Refresh() for that as far as I know. I want the Expander's Header to update as the ListBox's Item does, that is, while the user is typing.
The ListBox's DataContext is an observable collection of the class Contact. The Contact class has a property called ListString with a get that returns the result of the method ListItem(). The ListBox's ItemTemplate simply binds to the ListString property. The Expander's Header is bound to ListBox's SelectedItem.ListString and currently is only updated when selecting different ListBox items. I need it to update as typing occurs.
Below is the XAML and C# code behind code. The controls to the right of the ListBox are invisible until an entry is selected in the ListBox. The [New] button inserts a new item into the ListBox and selects it, thereby casing the controls to the right to appear and focus given to the Name field. As you type into the Name field and/or the Phone field, the corresponding item in the ListBox will update, but not the Expander's Header. That does not update until you select another item in the ListBox. I want it to update at the same time the ListBox item updates. How do I do that?
<Window x:Class="Binding_List_Expander_04.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Binding List Expander 04"
Height="350"
Width="530">
<Window.Resources>
</Window.Resources>
<Grid>
<StackPanel Orientation="Horizontal" Margin="3">
<StackPanel Orientation="Vertical" Margin="3">
<ListBox Name="ContactList"
ItemsSource="{Binding}"
Width="166"
Height="270"
Margin="0,0,0,3">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=ListString}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Name="NewItem"
Content="New"
Click="Event_NewContact_Click"
Height="23"
Width="75" />
</StackPanel>
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<Style TargetType="ScrollViewer">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ContactList, Path=SelectedIndex}" Value="-1">
<Setter Property="Opacity" Value="0" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<ScrollViewer Height="302">
<StackPanel Orientation="Vertical">
<Expander Name="ContactExpander">
<Expander.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding ElementName=ContactList, Path=SelectedItem.ListString}" />
</DataTemplate>
</Expander.HeaderTemplate>
<StackPanel Margin="21,0,0,0"
Orientation="Vertical">
<Grid Margin="3"
TextBoxBase.TextChanged="Event_ContactName_TextChanged">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="3" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="3" />
<ColumnDefinition Width="250" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0"
Grid.Column="0"
Text="Name:" />
<TextBox Grid.Row="0"
Grid.Column="2"
Name="ContactName"
Text="{Binding ElementName=ContactList, Path=SelectedItem.Name, Mode=TwoWay}" />
<TextBlock Grid.Row="2"
Grid.Column="0"
Text="Phone:" />
<TextBox Grid.Row="2"
Grid.Column="2"
Name="ContactPhone"
Text="{Binding ElementName=ContactList, Path=SelectedItem.Phone, Mode=TwoWay}" />
</Grid>
</StackPanel>
</Expander>
<Expander Header="
This is a place holder, there will be
many Expanders following this one."
Margin="0,10,0,0">
<StackPanel>
<TextBlock Text="Data and
Information" FontSize="30" TextAlignment="Center" />
</StackPanel>
</Expander>
<Expander Header="This is another place holder."
Margin="0,10,0,0">
<StackPanel>
<TextBlock Text="Data and
Information" FontSize="30" TextAlignment="Center" />
</StackPanel>
</Expander>
<Expander Header="This is another place holder."
Margin="0,10,0,0">
<StackPanel>
<TextBlock Text="Data and
Information" FontSize="30" TextAlignment="Center" />
</StackPanel>
</Expander>
</StackPanel>
</ScrollViewer>
</StackPanel>
</StackPanel>
</Grid>
</Window>
using System.Windows;
using System.Collections.ObjectModel;
using System.Windows.Threading;
using System.Threading;
using System.Windows.Controls;
namespace Binding_List_Expander_04
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ObservableCollection<Contact> Contacts = new ObservableCollection<Contact>();
public MainWindow()
{
InitializeComponent();
ContactList.DataContext = Contacts;
}
private void Event_NewContact_Click(object sender, RoutedEventArgs e)
{
Contacts.Insert(0, new Contact());
ContactList.SelectedIndex = 0;
if (ContactExpander.IsExpanded)
SetFocus(ContactName);
else
{
ContactExpander.IsExpanded = true;
SetFocus(ContactName);
}
}
public void SetFocus(UIElement control)
{
control.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, (ThreadStart)delegate { control.Focus(); });
}
private void Event_ContactName_TextChanged(object sender, TextChangedEventArgs e)
{
var tb = e.Source as TextBox;
Contact C = ContactList.SelectedItem as Contact;
if (tb == ContactName)
C.Name = tb.Text;
else if (tb == ContactPhone)
C.Phone = tb.Text;
ContactList.Items.Refresh();
}
}
public class Contact
{
public string Name { get; set; }
public string Phone { get; set; }
public string ListString { get { return ListItem(); } } // See comments in ListItem() below.
public Contact()
{
Name = string.Empty;
Phone = string.Empty;
}
private string ListItem()
{/*
* This is a simplified version, the actual version is complicated and cannot be templatized.
* Please, do not suggest templitazing this. I know this simple version can be templitazed,
* but the actual version cannot be templatized. I need to know how to make this work as it
* currently is.
*/
if ((Name + Phone).Trim().Length == 0)
return "<New Contact>";
else
{
string li = Name.Trim();
if (li.Length != 0 && Phone.Trim().Length != 0) li += ": ";
return li + Phone.Trim();
}
}
}
}
Thanks for you help.

Grid height not adjusting properly when a row height with SharedSizeGroup changes

I have two grids with three rows each. The first and last row of each grid has a fixed height while the middle rows have auto height and share their height using SharedSizeGroup.
First, the content of the right grid determines the height of the size group. When the content of the right grid shrinks so that the content of the left grid determines the height of the size group, the overall size of the left grid is not adjusted correctly.
See the following sample app. Click the increase button to increase the size of the textblock in the right grid. The size of the left grid changes accordingly. Now decrease the size. When the textblock becomes smaller than the textblock in the left grid, the total size of the left grid doesn't shrink. Is this a bug or am i missing something?
<StackPanel Orientation="Horizontal" Grid.IsSharedSizeScope="True">
<StackPanel Orientation="Vertical" Width="100">
<Grid Background="Green">
<Grid.RowDefinitions>
<RowDefinition Height="15" />
<RowDefinition SharedSizeGroup="Group1" />
<RowDefinition Height="15" />
</Grid.RowDefinitions>
<TextBlock Background="Blue" Grid.Row="0" />
<TextBlock Background="Red" Grid.Row="1" Name="textBlock1" Height="100" />
<TextBlock Background="Blue" Grid.Row="2" />
</Grid>
<TextBlock Text="{Binding Path=ActualHeight, ElementName=grid1}" />
</StackPanel>
<StackPanel Orientation="Vertical" Width="100">
<Grid Background="Green">
<Grid.RowDefinitions>
<RowDefinition Height="15" />
<RowDefinition SharedSizeGroup="Group1" />
<RowDefinition Height="15" />
</Grid.RowDefinitions>
<TextBlock Background="Blue" Grid.Row="0" />
<TextBlock Background="Red" Grid.Row="1" Name="textBlock2" Height="150" />
<TextBlock Background="Blue" Grid.Row="2" />
</Grid>
<TextBlock Text="{Binding Path=ActualHeight, ElementName=grid2}" />
</StackPanel>
<Button Height="24" Click="Button_Click_1" VerticalAlignment="Top">Increase</Button>
<Button Height="24" Click="Button_Click_2" VerticalAlignment="Top">Decrease</Button>
</StackPanel>
private void Button_Click_1(object sender, RoutedEventArgs e)
{
textBlock2.Height += 30;
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
textBlock2.Height -= 30;
}
The rows are staying the same height - the size of the second TextBlock is changing, while the size of the first TextBlock remains 100.
To demonstrate this, make the following changes:
Add ShowGridLines="True" to each of your Grids
Change your named TextBlocks to show their ActualHeight as their text:
<TextBlock Background="Red" Grid.Row="1" Name="textBlock2" Height="150"
Text="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"/>
You will see that the SharedSizeGroup row will be the maximum ActualHeight of the two TextBlocks.
Update: A Short Project To Show What's Happening
I've created a quick-n-dirty project to show what's happening. It turns out that when the right grid gets smaller than the original size, the left grid does an Arrange but not a Measure. I am not sure I understand this completely - I have posted a follow-up question with this same solution.
Here is the solution that I created, based on your original. It simply wraps the basic grid in a custom class that spews out info (via event) when Measure and Arrange are called. In the main window, I just put that info into a list box.
InfoGrid and InfoGridEventArgs classes
using System.Windows;
using System.Windows.Controls;
namespace GridMeasureExample
{
class InfoGrid : Grid
{
protected override Size ArrangeOverride(Size arrangeSize)
{
CallReportInfoEvent("Arrange");
return base.ArrangeOverride(arrangeSize);
}
protected override Size MeasureOverride(Size constraint)
{
CallReportInfoEvent("Measure");
return base.MeasureOverride(constraint);
}
public event EventHandler<InfoGridEventArgs> ReportInfo;
private void CallReportInfoEvent(string message)
{
if (ReportInfo != null)
ReportInfo(this, new InfoGridEventArgs(message));
}
}
public class InfoGridEventArgs : EventArgs
{
private InfoGridEventArgs()
{
}
public InfoGridEventArgs(string message)
{
this.TimeStamp = DateTime.Now;
this.Message = message;
}
public DateTime TimeStamp
{
get;
private set;
}
public String Message
{
get;
private set;
}
}
}
Main Window XAML
<Window x:Class="GridMeasureExample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:GridMeasureExample"
Title="SharedSizeGroup" Height="500" Width="500">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Column="0"
Grid.Row="0"
Orientation="Horizontal"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Grid.IsSharedSizeScope="True">
<StackPanel Orientation="Vertical" Width="100">
<local:InfoGrid x:Name="grid1" Background="Green" ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="15" />
<RowDefinition SharedSizeGroup="Group1" />
<RowDefinition Height="15" />
</Grid.RowDefinitions>
<TextBlock Background="Blue" Grid.Row="0" Text="Row 0"/>
<TextBlock Background="Red" Grid.Row="1" Name="textBlock1" Height="100"
Text="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"/>
<TextBlock Background="Blue" Grid.Row="2" Text="Row 2" />
</local:InfoGrid>
<TextBlock Text="{Binding Path=ActualHeight, ElementName=grid1}" />
</StackPanel>
<StackPanel Orientation="Vertical" Width="100">
<local:InfoGrid x:Name="grid2" Background="Yellow" ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="15" />
<RowDefinition SharedSizeGroup="Group1" />
<RowDefinition Height="15" />
</Grid.RowDefinitions>
<TextBlock Background="Orange" Grid.Row="0" Text="Row 0" />
<TextBlock Background="Purple" Grid.Row="1" Name="textBlock2" Height="150"
Text="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"/>
<TextBlock Background="Orange" Grid.Row="2" Text="Row 2" />
</local:InfoGrid>
<TextBlock Text="{Binding Path=ActualHeight, ElementName=grid2}" />
</StackPanel>
</StackPanel>
<ListBox x:Name="lstInfo"
Grid.Column="1"
Grid.Row="0"
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
<UniformGrid Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="2"
Columns="2"
HorizontalAlignment="Center"
Margin="5">
<Button x:Name="btnIncrease" Margin="4,0">Increase</Button>
<Button x:Name="btnDecrease" Margin="4,0">Decrease</Button>
</UniformGrid>
</Grid>
</Window>
Main Window Constructor (only code in code-behind)
public Window1()
{
InitializeComponent();
btnIncrease.Click += (s, e) =>
{
lstInfo.Items.Add(String.Format("{0} Increase Button Pressed", DateTime.Now.ToString("HH:mm:ss.ffff")));
textBlock2.Height += 30;
};
btnDecrease.Click += (s, e) =>
{
lstInfo.Items.Add(String.Format("{0} Decrease Button Pressed", DateTime.Now.ToString("HH:mm:ss.ffff")));
if (textBlock2.ActualHeight >= 30)
textBlock2.Height -= 30;
};
grid1.ReportInfo += (s, e) => lstInfo.Items.Add(String.Format("{0} Left Grid: {1}", e.TimeStamp.ToString("HH:mm:ss.ffff"), e.Message));
grid2.ReportInfo += (s, e) => lstInfo.Items.Add(String.Format("{0} Right Grid: {1}", e.TimeStamp.ToString("HH:mm:ss.ffff"), e.Message));
}

WPF/XAML bind width of an element to a fraction of screen size

I am writing an application in C#/WPF and am trying to figure out how to databind the width of the grids column definitions to a fraction of the screen width. Is this possible? Essentially I want something like this:
Grid = 2x2
Row 1 Height = 2/3 of screen height
Row 2 Height = 1/3 of screen height
Row 1 Width = 2/3 of screen width
Row 2 Width = 1/3 of screen width
I think that this correctly binds the full width to a column definition:
<ColumnDefinition Width="{Binding ElementName=Window1, Path=Width}"/>
but what I don't know how to do is perform an operation on the value it gets through the databinding... is this even possible? I feel like this is something I should be able to code into the XAML and not have to implement programmatically but I have little experience with UI design :( I would want something like:
<ColumnDefinition Width="{Binding ElementName=Window1, Path=Width} * 2 / 3"/>
but that is invalid
Should I just be writing a function to re-layout UI elements whenever the screen resizes? I feel like that is redundant... or is there some easy way of doing this that I don't know about? Any input welcome! Thanks!
It sounds like you just want to use star sizing:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
This will always give the first row 2/3 of the height and the second row 1/3 of the height, and the first column 2/3 of the width and the second column 1/3 of the width. It will be based on the size of the Grid, but if the Grid is the only child of the Window then that will be the same as the size of the Window.
I ran into a situation where I needed to bind the width of a control to a fraction of the height of another and I don't think there's a handy built-in way of dividing a bound property like that. I ended up making a converter that would divide a bound value by the converter parameter to solve this issue without resorting to code-behind and figured I'd share it in case it helps save someone else some time. I'm posting it here because this post was the first result when I was searching if there was a cleaner way.
Converter class:
[ValueConversion(typeof(double), typeof(double))]
public class DoubleDivisionConverter : System.Windows.Data.IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if(value == null) { return 0.0; }
if(parameter == null) { return value; }
double param;
if(Double.TryParse(parameter.ToString(), out param))
{
if(param == 0) { return 0.0; }
return (double)value / param;
}
else
{
throw new ArgumentException("Could not parse converter parameter as double.");
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if(value == null) { return 0.0; }
if(parameter == null) { return value; }
double param;
if(Double.TryParse(parameter.ToString(), out param))
{
if(param == 0) { return 0.0; }
return (double)value * param;
}
else
{
throw new ArgumentException("Could not parse converter parameter as double.");
}
}
}
XAML: (Converter used on UniformGrid Width property)
<Window.Resources>
<converters:DoubleDivisionConverter x:Key="DoubleDivisionConverter"/>
</Window.Resources>
<Grid x:Name="sampleGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.Resources>
<Style TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</Grid.Resources>
<Border x:Name="rSample" Grid.Column="0" Grid.Row="0" Background="PaleVioletRed">
<TextBlock>
<TextBlock.Inlines>
<Run Text="{Binding ElementName=rSample, Path=ActualWidth, StringFormat={}{0:N1}px, Mode=OneWay}"/><Run Text=", "/><Run Text="{Binding ElementName=rSample, Path=ActualHeight, StringFormat={}{0:N1}px, Mode=OneWay}"/>
</TextBlock.Inlines>
</TextBlock>
</Border>
<!-- Set the UniformGrids width to 1/3 the height of the parent grid using the converter -->
<UniformGrid Grid.Column="1" Grid.Row="0" Columns="1"
Width="{Binding ElementName=sampleGrid, Path=ActualHeight, Converter={StaticResource DoubleDivisionConverter}, ConverterParameter=3, Mode=OneWay}">
<Border x:Name="gSample" Background="LightGreen">
<TextBlock>
<TextBlock.Inlines>
<Run Text="{Binding ElementName=gSample, Path=ActualWidth, StringFormat={}{0:N1}px, Mode=OneWay}"/><Run Text=", "/><Run Text="{Binding ElementName=gSample, Path=ActualHeight, StringFormat={}{0:N1}px, Mode=OneWay}"/>
</TextBlock.Inlines>
</TextBlock>
</Border>
<Border x:Name="ySample" Background="LightGoldenrodYellow">
<TextBlock>
<TextBlock.Inlines>
<Run Text="{Binding ElementName=ySample, Path=ActualWidth, StringFormat={}{0:N1}px, Mode=OneWay}"/><Run Text=", "/><Run Text="{Binding ElementName=ySample, Path=ActualHeight, StringFormat={}{0:N1}px, Mode=OneWay}"/>
</TextBlock.Inlines>
</TextBlock>
</Border>
<Border x:Name="bSample" Background="LightBlue">
<TextBlock>
<TextBlock.Inlines>
<Run Text="{Binding ElementName=bSample, Path=ActualWidth, StringFormat={}{0:N1}px, Mode=OneWay}"/><Run Text=", "/><Run Text="{Binding ElementName=bSample, Path=ActualHeight, StringFormat={}{0:N1}px, Mode=OneWay}"/>
</TextBlock.Inlines>
</TextBlock>
</Border>
</UniformGrid>
</Grid>
Result:

Categories

Resources