Align center until there isn't enough space WPF - wpf

I have a particular layout behavior in mind, and I want to see if anyone can come up with a simple way of doing this before I try to write a custom panel or something. Take a look at this animation I manually put together:
There is an element of fixed width to the far left (the rectangle), and then a second element that is centered- not within the remaining space, but relative to the entire container. As the container shrinks, the centered element stays centered until the left element gets too close. Instead of staying directly in the middle, the "centered" element now stays as close to the center as possible without overlapping. This is the behavior I want.
Using a Grid, I can do something like this:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Width="100" Background="Green"/>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Grid.ColumnSpan="2" FontSize="24" Margin="2, 0">Centered Text</TextBlock>
</Grid>
Which puts everything in the right place, but the TextBlock will start overlapping with the Border instead of moving further right.
So any idea how I can get this to do what I want?

Give this a try:
Name of Window x:Name="window1"
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border x:Name="border1" Width="100" Background="Green"/>
<TextBlock x:Name="textBlock1" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="24" Grid.ColumnSpan="2">Centered Text</TextBlock>
</Grid>
SizeChanged event
private void Window1_SizeChanged(object sender, SizeChangedEventArgs e) {
if(( ( window1.Width / 2 ) - ( textBlock1.ActualWidth / 2 ) - border1.Width - ( SystemParameters.ResizeFrameVerticalBorderWidth * 2 ) ) <= 0) {
Grid.SetColumn(textBlock1, 1);
textBlock1.HorizontalAlignment = HorizontalAlignment.Left;
} else {
Grid.SetColumn(textBlock1, 0);
textBlock1.HorizontalAlignment = HorizontalAlignment.Center;
}
}

Related

WPF: Calculating the size of a Grid Column

I have a grid that contains three columns.
<Grid Background="AliceBlue">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="8*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="2*" Name="ManualControlsSplit" />
</Grid.ColumnDefinitions>
The first column contains a Grid and a chart.
The second column contains a GridSplitter.
The third column contains a StackPanel which contains a number of TextBlocks, Buttons and Grids containing TextBlocks and Buttons. Text size is dynamic and based on translation resources.
I need to calculate the minimum width that the contents of the third column would ideally like to be able to be drawn into so that the contents are not clipped.
My knowledge of WPF is limited to what I can google so any help would be appreciated.
With layout in WPF, you can allow one if not more of the Grid.Columns to be <ColumnDefinition Width="Auto"/>. This essentially tells WPF to allow as much space as the contained controls want. The use of Auto cascades too, so in StackPanel you refer to, you can make that controls width Auto as well, likewise with the items it contains; if the stack panel merely contains a TextBlock (via some template or whatever), then you can also set this width to Auto and the width with set itself according to the contained text.
<Grid Background="AliceBlue">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
...
<StackPanel Grid.Column=2
Width="Auto">
<TextBlock Width="Auto"
Text="This is TextBlock"/>
...
</StackPanel>
...
</Grid>
In this case the Text of the TextBlock sets the width of the StackPanel, which in turn sets the width of the 3rd grid column.
I hope this helps.
My WPF XAML setup is as follows (bits removed to keep this simple):
<UserControl ....>
<Grid Background="AliceBlue" Name="TopGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="12*" Name="Graph" />
<ColumnDefinition Width="5" Name="GridSplitter" />
<ColumnDefinition Width="2*" Name="ManualControlsSplit" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Background="AliceBlue"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<!-- A Graph and simple controls -->
</Grid>
<!-- GRID SPLITTER -->
<GridSplitter Grid.Column="1" Width="5"
HorizontalAlignment="Stretch" Name="Splitter" />
<Label Grid.Column="1" Content="⁞" Foreground="White"
Background="DarkGray"
VerticalAlignment="Center" FontSize="26" FontWeight="Bold"
IsHitTestVisible="False"/>
<!-- end GRID SPLITTER -->
<StackPanel Grid.Column="2" Grid.Row="0" Margin="5"
Name="TemperatureControls">
<!-- Load of Controls -->
</StackPanel>
</Grid>
</UserControl>
To calculate the desired width I use the following code:
// get my UserControl object
var manualControlView = userControl as HeatingController.Views.ManualControlView;
// Query the current width of THIRD column
double actualWidth = manualControlView.ManualControlsSplit.ActualWidth;
// Set up a BIG size (this has to be bigger size than the contents of the
// THIRD column would ever need)
Size size = new Size(400, manualControlView.TopGrid.ActualHeight);
// Ask WPF layout to calculate the Desired size.
manualControlView.TemperatureControls.Measure(size);
double width = manualControlView.TemperatureControls.DesiredSize.Width;
if (actualWidth <= width)
{
// Too small - do something
}
else
{
// big enough - do something else.
}
The variable 'width' now contains the value I wanted to calculate.

Preventing StackPanel items from overlapping

I am designing a Windows Phone application and I would like to stack a TextBlock and a Button next to each other horizontally. This is my pseudo-code on what I would like to achieve:
<StackPanel Name="titlePanel" Orientation="Horizontal" Margin="0,-6.5,0,26.5">
<TextBlock Name="titleBox" Text="{Binding Title}" Style="{ThemeResource HeaderTextBlockStyle}" CharacterSpacing="{ThemeResource PivotHeaderItemCharacterSpacing}"/>
<Button Content="press" Width="whatever space is left" Length="what the width is"/> <!--how do I do this?-->
</StackPanel>
I got an error when I tried to put Width="*", so I'm looking to do something like Width="titlePanel.Width - titleBox.Width" and Length="this.Width", only I can't seem to be able to reference other objects inside XAML. I don't want to put into the code-behind file to format the size of the Button every time that page comes up... how would this be achieved?
You can't do it with StackPanel because, the stack panel measures every child element with positive infinity as the constraint for the axis that it is stacking elements along. The child controls have to return how big they want to be (positive infinity is not a valid return from the MeasureOverride in either axis) so they return the smallest size where everything will fit. They have no way of knowing how much space they really have to fill.
So you should use grid to achieve desired behavior.
You can use grid control for this kind of purpose
try this one
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Name="titleBox" Grid.Column="0" Margin="5,0" HorizontalAlignment="Stretch" TextWrapping="Wrap" Text="Your text goes here." />
<Button Content="press" Grid.Column="1" Margin="5,0" HorizontalAlignment="Stretch"/>
</Grid>
Hope this helps.

WPF GridSplitter behavior when resizing its parent Window

I have defined my GridSplittler XAML like the following.
<Window>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>
<Border Grid.Column="0" Background="Red" />
<GridSplitter Grid.Column="1" ResizeDirection="Columns" Width="3" Height="Auto" HorizontalAlighment="Stretch" VerticalAlignment="Stretch" />
<Border Grid.Column="0" Background="Green" />
</Window>
This will create two columns with a grid splitter between them and it will correctly resize the columns as the grid splitter is dragged left and right. Now if you resize the whole Window I would like the width of left red column to remain fixed and have the width of the right green column change (as the Window is resized). Its the same effect as when you resize the whole Visual Studio application and the Solution Explorer width remains fixed but the width of a code tab changes. Also the same as the SQL Server Management Studio; the Object Explorer width remains fixed but the SQL tabs width changes. I realize that these two examples use a more complicated docking control but I was hoping to achieve the same result with using the WPF GridSplitter.
Edit: Based on mathieu suggestions all the was necessary was to give the first column definition an initial width (shown using 200 below).
<Grid Name="GridName">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="Red" />
<GridSplitter Name="SplitterName" Grid.Column="1" ResizeDirection="Columns" Width="3" Height="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<Border Grid.Column="2" Background="Green" />
</Grid>
Now when the Window is re-sized it only changes the width of the green border and the width of the red border remains fixed. Thanks!
If you don't mind some code behind, you can achieve this behaviour by reacting to the DragDelta event of the splitter, and adjusting the width of the first column, by removing the Star width, and fixing the width according to the drag delta.
Xaml :
<Grid Name="GridName">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="Red" />
<GridSplitter Name="SplitterName" Grid.Column="1" ResizeDirection="Columns" Width="3" Height="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<Border Grid.Column="2" Background="Green" />
</Grid>
Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
SplitterName.DragDelta += SplitterNameDragDelta;
}
private void SplitterNameDragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
GridName.ColumnDefinitions[0].Width = new GridLength(GridName.ColumnDefinitions[0].ActualWidth + e.HorizontalChange);
}
}
I've used the snipet of mathieu - and modified it for code behind use.
In my case I need a dynamic number of Items resized by the GridSplitter.
I used the Thumb Control to have full control of the action
private Thumb GetNewThumbAsGridSplitter(int column)
{
var gs = new Thumb();
gs.SetValue(Grid.ColumnProperty, column);
// gs.SetValue(Grid.RowSpanProperty, 2);
// gs.SetValue(Grid.RowProperty, 1);
gs.Width = 5;
gs.MouseEnter += (o, i) =>
{
Mouse.OverrideCursor = Cursors.ScrollWE;
};
gs.MouseLeave += (o, i) =>
{
Mouse.OverrideCursor = Cursors.Arrow;
};
gs.DragDelta += (o, i) =>
{
var grid = (Grid)gs.Parent;
var previous = (YourControl)((grid.Children[column - 1]));
var next = (YourControl)(grid.Children[column + 1]);
if (next.MinWidth >= (next.ActualWidth - i.HorizontalChange))
{
return;
}
if (previous.MinWidth >= (previous.ActualWidth + i.HorizontalChange))
{
return;
}
previous.Width = previous.ActualWidth + i.HorizontalChange;
next.Width = next.ActualWidth - i.HorizontalChange;
};
return gs;
}

How to I make an Expander whose expanded size can be controlled via a GridSplitter? [duplicate]

I would like to have something like a resizable Expander. My basic idea was something like this:
<Grid HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="2" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Expander Grid.Column="0" ExpandDirection="Right">
...
</Expander>
<GridSplitter Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
...
</Grid>
The problem with this: if i move the grid splitter and collaps the expander i got a big empty area. How can make the entire column collapse? Or is there another way to make the expander "resizable"
Not sure what you are trying to accomplish but i think conceptually the Grid should be part of the Expander.Content, would this work for you?
<Expander Header="Test" ExpandDirection="Right" HorizontalAlignment="Left" Background="LightBlue">
<Expander.Content>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="5"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Lorem ipsum dolor sit"/>
<GridSplitter Grid.Column="1" Width="5" ResizeBehavior="PreviousAndCurrent" ResizeDirection="Columns"/>
</Grid>
</Expander.Content>
</Expander>
Edit: Removed all the triggering from the first column as it seemed unnecessary.
Also: For this to work vertically the GridSplitter's HorizontalAlignment must be set to Stretch, otherwise it will have zero width by default (of course everything else that is orientation-specific must be adapted as well but that is straightforward)
HorizontalAlignment is the Microsoft .NET property accessor for what is in reality a dependency property. This particular dependency property quite frequently has its apparent "default" value set differently in subclassed elements, particularly controls. [...] For example, the apparent "default" of HorizontalAlignment for a Label control will be Left, even though Label inherits HorizontalAlignment direct from FrameworkElement. This is because that value was reset within the default style of Label, within the style's control template.
Maybe this will help to solve your "column collapse" problem
XAML:
Add in <Grid> Name="expGrid" and add in <Expander> Collapsed="Expander_Collapsed"
C# Code:
private void Expander_Collapsed(object sender, RoutedEventArgs e)
{
var colomnIndex = Grid.GetColumn(sender as Expander);
var colomn = expGrid.ColumnDefinitions[colomnIndex];
colomn.Width = GridLength.Auto;
}

How to have a click-able button in my combo-box ItemTemplate?

This is what i have so far:
<dxe:ComboBoxEdit Name="cboUserCustomReports"
Width="300" Height="Auto"
Margin="0,5,0,5"
ItemsSource="{Binding Path=UserReportProfileList,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"
EditValue="{Binding Path=UserReportProfileID,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
ValueMember="UserReportProfileID"
DisplayMember="ReportName"
PopupClosed="cboUserCustomReports_PopupClosed">
<dxe:ComboBoxEdit.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding ReportName, Mode=Default}"
VerticalAlignment="Stretch" HorizontalAlignment="Left"/>
<Button Name="btnDelete"
Grid.Column="1"
Width="20" Height="20"
VerticalAlignment="Center" HorizontalAlignment="Right"
Click="btnDelete_Click">
<Button.Template>
<ControlTemplate>
<Image Source="/RMSCommon;component/Resources/Delete.ico"></Image>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</DataTemplate>
</dxe:ComboBoxEdit.ItemTemplate>
</dxe:ComboBoxEdit>
First, i want the two columns to be stand alone. The user must be able to select or delete the item.
Second, i would like to make my button in the ItemTemplate to be click-able.
What do i need to add to get this behavior?
This is what it looks like at the moment:
Click
I assume, that your button is clickable, and you want to know how to process the click event. Right?
For the click-handler, add the following code:
private void btnDelete_Click(object sender, RoutedEventArgs e) {
FrameworkElement fe = sender as FrameworkElement;
if(null == fe){
return;
}
UserReportProfile userReportProfile = fe.DataContext as UserReportProfile;
if (null == userReportProfile) {
return;
}
// Do here your deletion-operation
}
I assumed that your item-class is named UserReportProfile. Otherwise, change the declared type accordingly.
Layout
For the alignment, add the following declaration to your ComboBox:
HorizontalContentAlignment="Stretch"
This gives your DataTemplate-Grid the full width and you can layout then your items as you desire.
<dxe:ComboBoxEdit Name="cboUserCustomReports"
HorizontalContentAlignment="Stretch"
Width="300" Height="Auto"
Margin="0,5,0,5"
...>
Your question is not clear enough. But I guess you want to vertically align the text and images in your combobox. If so, then all you need to do this:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
And I think your items are already clickable!

Resources