WPF Element Binding With Dynamically Generated UI Elements - wpf

I have a page in which I have a grid with 2 columns, one accommodating 80* width and the other accommodating 20*. Beneath the grid is a stack panel to which I load UI elements (child stack panels) at runtime.
My objective is to bind the widths of the stackpanels to the columns of the grid. This works perfectly and the stackpenels resize if the dynamic content is mocked to be static in the design view. But when the application is run, the bindings fail and the widths are not bound.
Is there any way to refresh the bindings so that I can notify the stackpanels to refer the widths of the grid and configure its's size?
Updated:
This is what my XAML looks like:
<Page x:Class="WPFTestApp.Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Page1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid x:Name="resizeGrid"
Grid.Column="0" Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="leftColumn"
Width="80*" />
<ColumnDefinition x:Name="rightColumn"
Width="20*" />
</Grid.ColumnDefinitions>
</Grid>
<StackPanel x:Name="contentPanel"
Grid.Column="0" Grid.Row="0" >
<!-- Dynamic StackPanels are added here -->
</StackPanel>
</Grid>
</Page>
In my code, I create StackPanel elements and add it to the contentPanel by saying:
contentPanel.Children.Add(...);
Unfortunately, I HAVE to use StackPanels here. :-(
The markup of a dynamically created StackPanel element is as follows (note that I use a Binding to the grid already in the XAML):
<StackPanel x:Name="element01"
Orientation="Horizontal"
Width="{Binding ElementName=resizeGrid, Path=ActualWidth}" >
<StackPanel x:Name="leftPanel"
Orientation="Vertical"
Width="{Binding ElementName=leftColumn, Path=ActualWidth}">
<!-- Content goes here -->
</StackPanel>
<StackPanel x:Name="rightPanel"
Orientation="Vertical"
Width="{Binding ElementName=rightColumn, Path=ActualWidth}">
<!-- Content goes here -->
</StackPanel>
</StackPanel>
The XAML code for my dynamic StackPanel elements are generated through an XSLT transformation
Note the xmlns:x namespace dynamic StackPanel. This also has to be done because of it being generated through XSLT

As far as I know setting the Width of a StackPanel with Orientation="Horizontal" will have no effect. The size of a StackPanel does not exceed the size of its content in its oriented direction.
Why do you "HAVE" to use StackPanels here? You almost certainly want DockPanels, given your objective. You won't have to bind or set anything, because the DockPanel will fill the cell automatically.

Try binding the ActualWidth instead of Width.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid x:Name="resizeGrid"
Grid.Column="0" Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="leftColumn"
Width="{Binding ElementName=contentPanel, Path=ActualWidth}" />
<ColumnDefinition x:Name="rightColumn"
Width="*" />
</Grid.ColumnDefinitions>
</Grid>
<StackPanel x:Name="contentPanel"
Grid.Column="0" Grid.Row="0" HorizontalAlignment="Left" >
<StackPanel Background="Yellow" Width="100" Height="25" />
<StackPanel Background="Green" Width="120" Height="25" />
<StackPanel Background="Red" Width="140" Height="25" />
<TextBlock Background="Gray" Text="{Binding ElementName=contentPanel, Path=ActualWidth}" />
</StackPanel>
</Grid>
The following changes I have done above:
1) Set the leftColumn to the ActualWidth of the contentPanel
2) Set the rightColumn to *
3) contentPanel HorizontalAlignment should be changed other then Stretch. Here I have changed it to Left
HTH

I found a solution for this issue. (Sorry for the late response)
What I did was, I cut and pasted the "resizeGrid" Grid control into the "contentPanel" Stackpanel.
As I mentioned above, the contents within the contentPanel are generated through an XSLT transformation and this surely creates a namespace reference issue. I noticed "element not found..." message in the Output window whenever this Window is loaded - this proved me right.
Inserting the "resizeGrid" into the "contentPanel" helped because then both the controls are within the same namespace and can be referenced for a PropertyBinding using "ElementName" attribute.
Thank you for your support! :-)

Related

Width of Textbox, which placed in grid column, extends when inserting long text

When I put a Textbox in a grid column like below
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="115"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="90"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="hello"/>
<TextBox Grid.Column="1" />
<Button Grid.Column="2" Content="push me"/>
</Grid>
</StackPanel>
I get proper result, i.e. textbox width is get from parent grid
But when I type a long text, the textbox starts exceeding its column and it stops extending after several extra letters
To .Net 4.6.2, I get same result but changing to .Net 4.7.2 the problem is solved i.e. the textbox width is not changing.
My software is compiled .Net 4.0, is there a solution to solve this for lower .net than 4.7.2?
Tried Pavel's first idea: removing stackpanel and adding another grid row in, still not working in .net 4.6.2
Tried Pavel's second idea: making the width of first column "Auto" instead of "1*". This works, the textbox doesn't extend (.net 4.6.2), however I really wanted the first and second column be responsive to resize.
You can solve this be removing the StackPanel and adding RowDefinition to Grid. You can also set TextWrapping="Wrap" for TextBox for wrapping a long text
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="90"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Text="hello" MinWidth="115"/>
<TextBox Grid.Column="1" TextWrapping="Wrap"/>
<Button Grid.Column="2" Content="push me"/>
</Grid>
StackPanel Y dimension is constrained to content, it will be automatically expanded to its content, more details can be found at panels overview
To me this is a bug that Textbox text size can change its parent width (grid width). This is happening in VS2019 .Net 4->4.6.2 but not in and after .Net 4.7.2.
For anybody faces this problem, I found below workaround by defining an invisible grid which contains textblocks. I get the widths of columns and give them to controls placed in a StackPanel.
<some container>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" MinWidth="115"/>
<ColumnDefinition Width="3*" />
<ColumnDefinition MinWidth="90"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0"/>
</Grid.RowDefinitions>
<TextBlock x:Name="C0" Grid.Column="0" />
<TextBlock x:Name="C1" Grid.Column="1" Margin="5"/>
<TextBlock x:Name="C2" Grid.Column="2" />
</Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Width="{Binding ElementName=C0, Path=ActualWidth }" Text="hello" />
<TextBox Width="{Binding ElementName=C1, Path=ActualWidth }" Margin="5" />
<Button Width="{Binding ElementName=C2, Path=ActualWidth }" Content="push me"/>
</StackPanel>
</some container>
The margin should be the same for column in grid and control in StackPanel (see second column).

Fixing a listbox's size in WPF

I have a Listbox that's inside a Grid that's inside a user control. That user control is placed in a tab panel that can resize.
The Listbox is defined as:
<ListBox HorizontalAlignment="Left" Margin="12,42,12,12" Name="listBox1" VerticalAlignment="Top" >
This works great as long as my listbox is populated. When it's not populated, it's a square about 4 pixels wide/tall. If it's not full, it shrinks down to fit whatever is in it.
This is not what I'd like. What I would like is for it to always maintain the margins I've defined. My question is in two parts:
1: Why does it behave the way it does?
2: How do I make it behave the way I want?
Thanks.
Since you're placing this on a Grid control, you should actually use it as intended and create the rows and columns and put your ListBox in the correct cell(s).
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="42" />
<RowDefinition />
<RowDefinition Height="12" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="12" />
<ColumnDefinition />
<ColumnDefinition Width="12" />
</Grid.ColumnDefinitions>
<ListBox x:Name="listBox1"
Grid.Row="1" Grid.Column="1">
</ListBox>
</Grid>
It is maintaining the margins but the margins are outside the listbox itself so only change the positioning of the listbox relative to its container.
I'm not sure how you would like it to appear but could set a minimum width/height.
<ListBox HorizontalAlignment="Left" Margin="12,42,12,12"
Name="listBox1" MinHeight="10" MinWidth="100" VerticalAlignment="Top" />
or just remove the alignment settings
<ListBox Margin="12,42,12,12" Name="listBox1" />
if you want the listbox to fill its container.

WPF Datagrids slow when using Auto column sizes

When I expand the Expander in the code below, the application becomes very sluggish and takes 2-3 seconds to respond to resize/move events triggered by the user. If I set the second column to <ColumnDefinition Width="250"/> response time remains optimal. What am I missing here? (I'm need the UI as per below but without the sluggishness)
<UserControl>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Toolbar .. />
<DataGrid Grid.Row="1" ItemsSource="{Binding collection1}" .. />
<Expander Grid.Column="1" Grid.RowSpan="2">
<DataGrid ItemsSource="{Binding collection2}" .. />
</Expander>
</Grid>
</UserControl>
FYI: I suspect Row Virtualization is not being used when Width=Auto is set and DataGridRow objects are being created for the entire bound data source...
UPDATE
The following also does not remove the sluggishness;
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width={Binding ElementName=expander, Path=Width} />
</Grid.ColumnDefinitions>
..
<Expander Name="expander" Grid.Column="1" Grid.RowSpan="2">
<DataGrid ItemsSource="{Binding collection2}" Width="300" .. />
</Expander>
The problem is that if you set the ColumnWidth to Auto, the column width will be calculated depending on the content, here, the dataGrid. So you will have the second column very large (even though you might not see it depending on your layout) and the dataGrid's columns will all be drawn every time.
So basically, you loose the benefits of columnVirtualization (not rowVirtualization).
This is the same reason why you should never put a DataGrid into a ScrollViewer.
Solution
Set VirtualizingStackPanel.IsVirtualizing="True" on your grid
if this does not work, you might have to take care of the resizing yourself, no real option there.

Workaround for Grid.SharedSizeGroup in Silverlight

There is no Grid.SharedSizeGroup in Silverlight 4. What is your workaround for this issue?
For example: I have a DataTemplate for ListBox.ItemTemplate consisting of a grid with two columns and I'd like to have the same width for both columns and the first column needs to have auto width.
Vote for this feature here:
http://dotnet.uservoice.com/forums/4325-silverlight-feature-suggestions/suggestions/1454947-sharedsizegroup
and also here:
http://connectppe.microsoft.com/VisualStudio/feedback/details/518461/sharedsizegroup-should-be-available-in-silverlight
SharedSize Grid with Silverlight - haven't tested it but looks usable.
Shared sizing is best implemented using element property bindings in Silverlight. Just make all your shared sized elements bind to the width/height of another.
EDIT:
I put a quick example of what I mean together. I'm not sure what you mean by using star sizing when you said in the question you want auto sizing -
<Grid Height="400"
Width="600"
Background="Gray">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button x:Name="parent"
Content="CHANGE ME TO ADJUST THE COLUMN SIZE"
Grid.Column="0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Background="Red" />
<Button Width="{Binding ActualWidth, ElementName=parent}"
Grid.Column="1"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Background="Blue" />
<Button Width="{Binding ActualWidth, ElementName=parent}"
Grid.Column="2"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Background="Yellow" />
</Grid>
HTH

Is there a better way than a grid to line up controls in WPF?

I am using a grid by the definition of appropriateness defined in this question Grid vs Stackpanel. However when working with grids you have to define the controls position inside them explicitly in the grid. This becomes a pain when having to reorder controls or when adding a new control to the grid. With the code provided as an example, is there a way to get the rows and columns for the text and text boxes to line up while being easy to modify or expand later?
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="7*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="Value One:" Grid.Row="0" Grid.Column="0"/>
<TextBox x:Name="TextBoxOne" Grid.Row="0" Grid.Column="1"/>
<TextBlock Text="Value Two:" Grid.Row="1" Grid.Column="0"/>
<TextBox x:Name="TextBoxTwo" Grid.Row="1" Grid.Column="1"/>
<TextBlock Text="Value Three:" Grid.Row="2" Grid.Column="0"/>
<TextBox x:Name="TextBoxThree" Grid.Row="2" Grid.Column="1"/>
</Grid>
I wrote a custom control I use that makes it extremely easy to do this, but before I created it I generally used this sort of thing:
<ControlTemplate x:Key="ColumnsTemplate" TargetType="HeaderedContentControl">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="7*" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0" ContentSource="Header" />
<ContentPresenter Grid.Column="1" />
</Grid>
</ControlTemplate>
<ItemsControl ... ItemTemplate="{StaticResource ColumnsTemplate}">
<HeaderedContentControl Header="Value One:">
<TextBox x:Name="TextBoxOne" />
</HeaderedContentControl>
<HeaderedContentControl Header="Value Two:">
<TextBox x:Name="TextBoxTwo" />
</HeaderedContentControl>
...
</ItemsControl>
This allows easy add/remove of items from the ItemsControl, or better yet, data binding.
If you prefer auto-sizing on the grid rather than star sizing (3* and 7*) you can use a shared sizing scope by setting IsSharedSizeScope on the ItemsControl and SharedSizeGroup on the first ColumnDefinition.
Another option is GridView, but I find it more difficult to use for this purpose.

Resources