I've been scratching my head over this one for a few days now. I'm trying to create a UserControl(s) that will allow me to simplify the process of creating I/O forms. In many forms I need something with a structure such as this:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto">
<ColumnDefinition Width="*">
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition>
<RowDefinition>
<RowDefinition>
...
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="..."/>
<TextBox Grid.Column="1" Grid.Row="0" Text="..."/>
<TextBlock Grid.Column="0" Grid.Row="1" Text="..."/>
<TextBox Grid.Column="1" Grid.Row="1" Text="..."/>
<TextBlock Grid.Column="0" Grid.Row="2" Text="..."/>
<TextBox Grid.Column="1" Grid.Row="2" Text="..."/>
...
<Grid>
What I'd like to write is something like this:
<mycontrols:ContainerControl>
<mycontrols:RowControl Label="Some label" Text="{Binding SomeProperty}">
<mycontrols:RowControl Label="Some label" Text="{Binding SomeProperty}">
<mycontrols:RowControl Label="Some label" Text="{Binding SomeProperty}">
<mycontrols:RowControl Label="Some label" Text="{Binding SomeProperty}">
</mycontrols:ContainerControl>
The RowControl's XAML would simply be a TextBlock and a TextBox style to my liking:
<TextBlock Text="{Binding Label}"/>
<TextBlock Text="{Binding Text}"/>
As I understand there are a few issues with this:
1.- In order that an element is affected by the grid layout, it has to be an immediate child of that Grid.
2.- The number of rows is arbitrary. In a Grid you need to specify each row definition.
If I could somehow have the Grid's layout affect not only immediate children but nested ones, I think I could do this. I'm also willing to solve this with a different approach if you can think of one.
The expected finally behavior is a mixture of a StackPanel and a Grid, which is:
~ An arbitrary amount of elements can be added without defining rows (just stack them).
~ The container is divided into two columns for, first takes the size of the biggest element contained (auto), and the second takes the rest of the width available (*).
All help is welcome! Thanks in advance :)
You might be able to leverage ColumnDefinition.SharedSizeGroup and the Grid.IsSharedSizeScope attached property. Try something like this:
<UserControl x:Class="CSharpTest.RowControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="RowLabel"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Label}"/>
<TextBlock Text="{Binding Text}" Grid.Column="1"/>
</Grid>
</UserControl>
And then to use it:
<mycontrols:ContainerControl Grid.IsSharedSizeScope="True">
<mycontrols:RowControl Label="Some label" Text="{Binding SomeProperty}"/>
<mycontrols:RowControl Label="Some label" Text="{Binding SomeProperty}"/>
<mycontrols:RowControl Label="Some label" Text="{Binding SomeProperty}"/>
<mycontrols:RowControl Label="Some label" Text="{Binding SomeProperty}"/>
</mycontrols:ContainerControl>
mycontrols:ContainerControl could be any type of container (e.g. StackPanel, UniformGrid, etc).
In the above, the first column of each RowControl's Grid will be uniform (they will all grow to accomidate the largest Label). Depending on what's desired, you might also need to set SharedSizeGroup on the second column (to a different value, something like "RowText").
Related
I'm looking for a way of implementing this kind of separator to the expander component in UWP app:
Any ideas how to do that?
I've managed to create the similar look of the expander, but I don't know how to implement separators?
Also, I would like to learn how to make expanders kind of "push" other components down when I expand them, so they don't overlap with other components, but simply move them.
Here's the XAML code for this expander:
<muxc:Expander x:Name="FontExpander"
x:Uid="FontExpander"
Width="1155"
IsExpanded="True"
Margin="22,323,0,0"
VerticalAlignment="Top"
Grid.Row="2"
Height="380"
HorizontalContentAlignment="Left"
ExpandDirection="Down">
<muxc:Expander.Header>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<FontIcon Margin="0,0,12,0"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
FontSize="16"
Glyph=""/>
<StackPanel Grid.Column="1"
Margin="0,12"
Orientation="Vertical">
<TextBlock x:Uid="SettingsFontTitle" Style="{StaticResource BodyTextBlockStyle}"/>
<TextBlock x:Uid="SettingsFontDescription"
Style="{StaticResource CaptionTextBlockStyle}"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="WrapWholeWords"/>
</StackPanel>
</Grid>
</muxc:Expander.Header>
<muxc:Expander.Content>
<Grid Margin="-18,-170,-17,-170">
<TextBlock x:Name="SettingsFamily" x:Uid="SettingsFamily" Text="Family" Margin="50,51,1000,286"/>
<ComboBox x:Name="ComboBoxFamily" ItemsSource="{x:Bind Fonts}" Width="200" Margin="950,41,0,0" SelectionChanged="ComboBoxFamily_SelectionChanged" />
<TextBlock x:Name="SettingsStyle" x:Uid="SettingsStyle" Text="Style" Margin="50,106,995,218" Loaded="SettingsStyle_Loaded"/>
<ComboBox x:Name="ComboBoxStyle" Width="200" Margin="950,101,0,0">
<x:String>Regular</x:String>
</ComboBox>
<TextBlock x:Name="SettingsSize" x:Uid="SettingsSize" Text="Size" Margin="50,166,1002,158"/>
<ComboBox x:Name="ComboBoxSize" ItemsSource="{x:Bind FontSizes}" Width="200" Margin="950,161,0,0"/>
<TextBlock x:Name="Text" x:Uid="SettingsText" Text="Hello World! I am so excited to be here!" Margin="62,224,38,126" TextWrapping="Wrap" FontSize="{x:Bind (x:Double)ComboBoxSize.SelectedValue, Mode=OneWay}" TextAlignment="Center"/>
</Grid>
</muxc:Expander.Content>
</muxc:Expander>
Any help would be much appreciated!
A separate can for example be implemented using a Border or a Grid:
<Grid Background="DarkGray" Height="1" />
As a side note, you shouldn't use margins to position your elements. Use an appropriate layout Panel. Using a Grid, you should add ColumnDefinitions and RowDefinitions to it as examplified here.
I have a grid with a text bar on top. This text bar shall show an error text and a "Close" link behind it. As soon as there is enough space, the "Close" text shall follow the error text immediately (upper image). If the grid's width decreases, the error text shall be trimmed, so that the "Close" text is still visible (lower image).
I tried all kinds of things: StackPanels, Grids, DockPanels, Width, MaxWidth,... and it's easy to have the "Close" text at the right edge of the grid, but I didn't succeed with just this requirement.
Here is an example of what I tried:
<DockPanel
<TextBlock
DockPanel.Dock="Left"
FontWeight="Bold"
Text="{Binding ErrorText}"
TextTrimming="CharacterEllipsis">
</TextBlock>
<TextBlock>
<Hyperlink Command="{Binding CloseCmd}">Close</Hyperlink>
</TextBlock>
</DockPanel>
The last element is supposed to fill the remaining space, but the upper textbox does not leave any space left for the "Close" element.
This should give you what you are after:
<Grid HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
FontWeight="Bold"
Text="This is some text that shall be trimmed if there is not enough room"
TextTrimming="CharacterEllipsis" />
<TextBlock Grid.Column="1">
<Hyperlink>Close</Hyperlink>
</TextBlock>
</Grid>
A colleague has given me a good solution:
<WrapPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" TextTrimming="CharacterEllipsis" Margin="5" Text="{Binding ErrorText}" />
<TextBlock Grid.Column="1" Margin="5" Text="Close" Foreground="Blue" />
</Grid>
The trick seems to be the WrapPanel, although nothing gets wrapped here. I have no clue why this works, but it does.
If I understand all requirements correctly, you should first put the close with dock=right like so:
<DockPanel>
<TextBlock DockPanel.Dock="Right">
<Hyperlink Command="{Binding CloseCmd}">Close</Hyperlink>
</TextBlock>
<TextBlock
FontWeight="Bold"
Text="{Binding ErrorText}"
TextTrimming="CharacterEllipsis">
</TextBlock>
</DockPanel>
Try using a WrapPanel with orientation set to vertical:
<Grid>
<WrapPanel Orientation="Vertical">
<TextBlock TextTrimming="CharacterEllipsis">Some sample text</TextBlock>
<Button>Close</Button>
</WrapPanel>
</Grid>
I am still trying to get my head around wpf ui creation. I have created a user control which has a expander, dockpanel and two comboboxes in the dockpanel with equal width hardcoded in xaml. It looks good during the design time and in runtime as long as the expander widths is unchanged. When the expander header is changed to a longer text the dockpanel streches and the two comboxes remain the same size. They are docked to the right hence there is a lot of gap between two comboxes.
My question, is there a way to equally space the two comboboxes in the dockpanel.
Code:
<DockPanel Height="Auto"
DockPanel.Dock="Top"
HorizontalAlignment="Stretch" Name="lhsInput"
VerticalAlignment="Stretch" Width="Auto"
LastChildFill="False">
<ComboBox Height="23" Name="cboLHSItem"
Width="84" FontWeight="Normal"
Margin="1,0,0,0" Foreground="Black" FontFamily="Tahoma" />
<ComboBox Height="23" DockPanel.Dock="Right"
Name="cboLHSValues" Width="79" FontWeight="Normal"
IsEditable="False" Margin="0,0,1,0" FontFamily="Tahoma"
Foreground="Black" />
</DockPanel>
You could use Grid instead of DockPanel, Sample code below:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ComboBox Height="23" Name="cboLHSItem"
MinWidth="79"
Grid.Column="0"
FontWeight="Normal"
Foreground="Black" FontFamily="Tahoma" />
<ComboBox Height="23" MinWidth="79"
Grid.Column="1"
Name="cboLHSValues" FontWeight="Normal"
IsEditable="False" FontFamily="Tahoma"
Foreground="Black" />
</Grid>
DockPanel may be providing the required Width for its Child elements. However, with Grid and its RowDefinitions and ColumnDefinitions you can specify whether you want "Auto" Width/Height or you want to use whatever is available for Width/Height. The above code specifies to use Whatever width available for the Child element by setting Width to * in ColumnDefinition.
UPDATE
To Understand how different Panels of WPF work, refer to MSDN page: Panels Overview
I have a Grid that I populate with a TextBlock and another Grid, which is initially hidden. Since I have the TextBlock to respond to click events, I only want the method to be called when the user clicks on the text itself, not anywhere in the Grid. This is what my code looks like right now:
<Grid x:Name="LayoutRoot" Height="25">
<TextBlock x:Name="NewLabelButton" Text="Add New" Width="Auto" Foreground="Blue" TextDecorations="Underline" MouseLeftButtonUp="AddNew_OnClick" VerticalAlignment="Center"/>
<Grid x:Name="NewLabelPanel" Visibility="Collapsed">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="NameBox" TextChanged="NameBox_OnTextChanged" KeyDown="NameBox_OnKeyDown" Grid.Column="0"/>
<Button x:Name="OKButton" Content="OK" Click="OKButton_OnClick" IsEnabled="False" Margin="2,0,0,0" Grid.Column="1"/>
<Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_OnClick" Margin="2,0,0,0" Grid.Column="2"/>
</Grid>
</Grid>
Which results in the TextBlock spanning the whole width of the Grid, making it clickable from essentially anywhere in the Grid. What I want is for it to only be as wide as it needs to be to display the text (which could vary, so I can't use a static width).
Using a StackPanel instead of a Grid resolves the TextBlock width issue, but then the child Grid doesn't expand to fill the entire StackPanel, which it needs to.
Encapsulating the TextBlock in a StackPanel works, but I'm wondering if there is a better (and more efficient) way of doing it.
<Grid x:Name="LayoutRoot" Height="25">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="NewLabelButton" Text="Add New" Foreground="Blue" TextDecorations="Underline" MouseLeftButtonUp="AddNew_OnClick" VerticalAlignment="Center"/>
</StackPanel>
<Grid x:Name="NewLabelPanel" Visibility="Collapsed">
So my question is this: Is there a better way to do this than with my little StackPanel hack, or is that the only way? Thanks!
Try setting the HorizontalAlignment of the TextBlock to the left of the grid.
<TextBlock x:Name="NewLabelButton" HorizontalAlignment="Left" Text="Add New" Width="Auto" Foreground="Blue" TextDecorations="Underline" MouseLeftButtonUp="AddNew_OnClick" VerticalAlignment="Center"/>
I am trying to get my data to display properly within a GridLayout, which is to be used as a DataTemplate for an Item within ListBox. Here is the code associated with what I am doing:
<Grid Name="FeedItemTemplate">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Source="{Binding ProfileImage}" Grid.RowSpan="2" Height="75" Width="75" VerticalAlignment="Center" Margin="1" />
<TextBlock Text="{Binding UserName}" Grid.Column="1" Foreground="#FFC8AB14" FontSize="28" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding TimeStamp}" Grid.Column="2" TextWrapping="Wrap" FontSize="18" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Message}" Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="2" TextWrapping="Wrap" FontSize="24" />
</Grid>
The issue is that using this layout, when TextWrapping is set to Wrap, the Item is displayed correctly, but when scrolling through the ListBox everything is really jittery, you cannot scroll in small increments, and it just jumps all over the place.
Any reason why it does this? As I said, only when TextWrapping is set to Wrap it does this. When its not used, it scrolls fine, but the text is all along one line and off the screen.
Does it keep jumping if you explicitly set the top-level Grid element's width to a fixed size?
For some reason that I do not fully understand, settings the ListBox's ItemsPanel property to a StackPanel might solve your problem:
<UserControl.Resources>
<ItemsPanelTemplate x:Key="MyItemsPanelTemplate">
<StackPanel/>
</ItemsPanelTemplate>
</UserControl.Resources>
...
<ListBox ... ItemsPanel="{StaticResource MyItemsPanelTemplate}"/>
This is a known issue with listbox scrolling in the current ctp when you have variable height items. The workaround for now is to set a fixed height on your listbox item content. You'll probably also notice the scroll bar doesnt properly go to the bottom all the time. The workaround fixes that too.
Reference.