Forcing layout update - wpf

How to force the layout measurements update?
I have simplified layout I am problem with; when you click the button first time you get one measurement and on the second click different one.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var w = mywindow.ActualWidth;
gridx.Width = w;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
btn3.Width = 100;
var w = mywindow.ActualWidth;
gridx.Width = w - btn3.Width;
InvalidateArrange();
InvalidateMeasure();
MessageBox.Show(btn1.ActualWidth.ToString());
}
Window
<Window x:Class="resizet.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded" Name="mywindow">
<DockPanel HorizontalAlignment="Stretch" LastChildFill="False">
<Grid HorizontalAlignment="Stretch" DockPanel.Dock="Left" Name="gridx">
<Button HorizontalAlignment="Stretch" Content="btn in grid" Click="Button_Click" />
</Grid>
<Button Name="btn2" Content="btn2" Width="0" DockPanel.Dock="Right" HorizontalAlignment="Left"></Button>
</DockPanel>
</Window>

This fixes the problem:
btn3.Width = 100;
btn3.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
var w = mywindow.ActualWidth;
gridx.Width = w - btn3.Width;
with additional
private static Action EmptyDelegate = delegate() { };

Changing the Width property must invalidate the layout on its own, you don't need to call InvalidateXXX() yourself.
The catch is that the layout is not updated immediately, but on the next iteration of the message loop. So the ActualWidth will not be changed immediately.
If you want the Grid to resize automatically when the button width is increasing, why not use the layout management and put the both into different columns of an outer Grid?
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid x:Name="gridx"
Grid.Column="0">
<Button HorizontalAlignment="Stretch"
Click="Button_Click"/>
</Grid>
<Button x:Name="btn2"
Content="btn2"
Width="0"
Grid.Column="1"/>
</Grid>
And in code-behind
private void Button_Click(object sender, RoutedEventArgs e)
{
btn2.Width = 100;
}

In a strict sense, #Daniel has provided some code that fixes the problem posed by the question. But the result is rather bad, putting layouting code into an event handler. The grid and the button might look good after the button got pressed, but once the user makes the window size bigger, the grid will not grow and will not use the available size. The user would have to press the button again to make the grid grow. That's most likely not how things should be and that's why #Vlad's answer is better.
WPF uses just one thread to process events and layouting, but they get executed in different phases. If width gets changed, the MeasureDirty flag of the control gets set, then the processing of the event continues immediately. Once this event and all other events needing processing are completed, only then starts WPF with the layouting (i.e. measure, arrange, render). Here is an overview how that works:
For a detailed description what happens in every step, see my article on CodeProject Deep Dive into WPF Layouting and Rendering
btn3.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
This statement halts the execution of the button event handler and forces a whole layouting / render phase to run, only then continues with the event handler code to change the width of the grid, which will force another layouting / render cycle to run.
Recommendations:
Do not set Height and Width in event handlers, unless you want them to be fixed and not to change, even the available space changes.
Use WPF controls like Grid, etc. to make best use of the available space
If you cannot find a WPF control like Grid which matches your layouting needs, write your own Control and put all the layouting code into MeasureOverride() and ArrangeOverride().

Related

How to implement popup in Windows Phone

I'm implementing a templated control, which should work as virtual keyboard button - when you hold it, it displays a popup with additional options to choose.
I've implemented the popup more less in the following way:
<Grid>
<Border>Content</Border>
<Grid x:Name="gPopup" Visibility="Collapsed">
<StackPanel x:Name="spSubItems" Orientation="Horizontal" />
</Grid>
</Grid>
I show the popup by changing visibility to visible and setting negative margins for top and bottom. However, when I do that, and when the popup is actually larger than the control, the control is being resized to match its size - despite fact, that it is not inside:
How can I implement the popup, such that it won't expand the container it's on? And such that the container will still match size of its contents?
Edit: In response to comments and answers
I'm not sure if I'm understood correctly. Here's an image with explanation:
I'd like to keep the original container's size the same after showing the popup. I'm unsure how WrapPanel or DockPanel could help me with that.
The solution is simply to use Popup instead of positioned Grid.
Sample- Create a grid
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<!-- Setting a Rectangle having transparent background which set the
Visibility of popup -->
<Rectangle Name="popupRect" Fill="#80000000" Visibility="Collapsed"/>
<!—Here in the above Code we are just filling the rectangle With the transparent BackGround -->
<!—Creating A Border -->
<Border Name="popupBorder" Background="{StaticResource PhoneChromeBrush}" BorderBrush="Red" BorderThickness="2"
HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Collapsed">
<!-- Creating A grid Inside the Border and Rectangle -->
</Grid>
Create event for which popup should appear(for both cancel and appear)-
private void cancelButton_Click(object sender, RoutedEventArgs e)
{
popupRect.Visibility = Visibility.Collapsed;
popupBorder.Visibility = Visibility.Collapsed;
}
private void popupButton_Click(object sender, RoutedEventArgs e)
{
popupRect.Visibility = Visibility.Visible;
popupBorder.Visibility = Visibility.Visible;
}
It will work, I guess.
Like spook says, put your gPopup Grid in a Popup element and show it by opening the popup. This won't affect the main visual tree.
The reason the embedded grid embiggens the border is that the outer grid has to expand to hold pGrid and the border expands to fill the outer grid.

Creating a square UserControl

I am designing a user control that needs to be square, and to fill as much room as it is given (to give some context, it is a checkboard).
My user control looks like:
<Grid>
<!-- My 8 lines / colums, etc. , sized with "1*" to have equal lines -->
</Grid>
Now I would simply like to say "This grid has to be square no matter what room it has to expand".
Tried Solutions in vain:
I can't use a UniformGrid because I actually have the names of the lines & columns in addition, so I have a leading header row and column with different sizes.
If I use a Viewbox with Uniform it messes all up.
I tried using the classic
<Grid Height="{Binding RelativeSource={RelativeSource Self}, Path=Width}"> ... </Grid>
but it only works if I manually set the Width property. Otherwise, this constraint is ignored.
Conclusion
I'm out of idea, and I would really like to avoid setting Width / Height manually as this control may be used in many various places (ListItem templates, games, etc...).
Solution from suggestion:
A solution is available with some code-behind. I did not find a XAML only solution.
Grid is now:
<Grid SizeChanged="Board_FullControlSizeChanged">...</Grid>
And the event handler is:
private void Board_FullControlSizeChanged(object sender, SizeChangedEventArgs args)
{
double size = Math.min (args.NewSize.Height, args.NewSize.Width);
((Grid)sender).Width = size;
((Grid)sender).Height = size;
}
I initially tried modifying your binding to ActualWidth and it still did not work when the Grid was the top level element and in some cases it ended up expanding the control further than the available size. Hence tried some other ways of getting the required output.
Got 2 ways of maybe addressing this:
Since this is a view related issue (not breaking MVVM, keeping a square formation, if your ok with having a bit of code-behind, you could do something like)
private void OnSizeChanged(object sender, SizeChangedEventArgs e) {
double minNewSizeOfParentUserControl = Math.Min(e.NewSize.Height, e.NewSize.Width);
mainGrid.Width = minNewSizeOfParentUserControl;
mainGrid.Height = minNewSizeOfParentUserControl;
}
and in your xaml you would name your main top level grid "mainGrid" and attach the UserControl size changed event handler to the above function not the Grid itself.
However if you totally hate code-behind for whatever reason, you can be a bit more fancy and create a behavior such as
public class GridSquareSizeBehavior : Behavior<Grid> {
private UserControl _parent;
protected override void OnAttached() {
DependencyObject ucParent = AssociatedObject.Parent;
while (!(ucParent is UserControl)) {
ucParent = LogicalTreeHelper.GetParent(ucParent);
}
_parent = ucParent as UserControl;
_parent.SizeChanged += SizeChangedHandler;
base.OnAttached();
}
protected override void OnDetaching() {
_parent.SizeChanged -= SizeChangedHandler;
base.OnDetaching();
}
private void SizeChangedHandler(object sender, SizeChangedEventArgs e) {
double minNewSizeOfParentUserControl = Math.Min(e.NewSize.Height, e.NewSize.Width);
AssociatedObject.Width = minNewSizeOfParentUserControl;
AssociatedObject.Height = minNewSizeOfParentUserControl;
}
}
For the behavior your xaml would then look like:
<Grid>
<i:Interaction.Behaviors>
<local:GridSquareSizeBehavior />
</i:Interaction.Behaviors>
</Grid>
Did test these two methods with Snoop and the square size was maintained while expanding/shrinking. Do note both methods in the crux use the same logic(just a quick mock-up) and you might be able to squeeze some better performance if you update the logic to only update height when width is changed and vice versa instead of both and canceling a resize all together if not desired
Try putting your grid in a ViewBox: http://msdn.microsoft.com/en-us/library/system.windows.controls.viewbox.aspx
Here's a code sample I came up with:
The usercontrol:
<UserControl x:Class="StackOverflow.CheckBoard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Viewbox>
<Grid Background="Red" Height="200" Width="200">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="testing" Grid.Row="0"/>
<Button Content="testing" Grid.Row="1"/>
<Button Content="testing" Grid.Row="2"/>
</Grid>
</Viewbox>
</UserControl>
And the main window:
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:AllNoneCheckboxConverter x:Key="converter"/>
</Window.Resources>
<Grid>
<StackPanel>
<local:CheckBoard MaxWidth="80"/>
</StackPanel>
</Grid>
</Window>
What this Viewbox will do is scale the control to the space it's given. Since the grid inside the viewbox is square, the grid will ALWAYS stay square. Try playing around with the MaxWidth property I used in the MainWindow.
You could bind the Height property to ActualWidth instead of Width:
<Grid Height="{Binding ActualWidth, RelativeSource={RelativeSource Self}}">
...
</Grid>
However, a better solution would be to use a Viewbox. The trick to avoid that it "messes all up" is to make its Child square by defining (sensible) equal values for Width and Height:
<Viewbox>
<Grid Width="500" Height="500">
...
</Grid>
</Viewbox>
Register wherever it suits you (usually in constructor or OnAttached()):
SizeChanged += Square;
and handle size with this:
private void Square(object sender, SizeChangedEventArgs e)
{
if (e.HeightChanged) Width = e.NewSize.Height;
else if (e.WidthChanged) Height = e.NewSize.Width;
}
I have solved this by setting the margin of the contained control from inside the parent control's size changed event.
In my case I have a 'sudoku grid' user control called SudokuBoard inside a standard Grid control called MainGrid (which fills the main window) and it only requires the following code;
private void MainGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
double verticalMargin = Math.Max((e.NewSize.Height - e.NewSize.Width)*0.5, 0.0);
double horizontalMargin = Math.Max((e.NewSize.Width - e.NewSize.Height)*0.5, 0.0);
SudokuBoard.Margin = new Thickness(horizontalMargin, verticalMargin, horizontalMargin, verticalMargin);
}

Silverlight simulate mouse click by X, Y?

If there is a way to send mouse click event by location programatically it would be great, but if theres another approach that can solve following problem this it is fine too.
In my situation I got a canvas taking up whole application size (covering it completely) and when user clicks it with mouse I want to hide it, and then pass through this mouse click (taking its location x & y from user) to anything that is under canvas (in my case canvas visibility goes to collapsed so controls under it can be seen now).
I am guessing it is impossible, cause certain features like run silverlight fullscreen can only be done in button click handler (correct me if im wrong here).
But is there a place where I can read about those security based limitations of silverlight UI ?
you have to add an click event handler to your canvas. In this handler you get the x and y positon of your click (via MouseButtonEventArgs) and then you can use the VisualTreeHelper to get your "hit elements".
Lets assume the following xaml:
<Grid x:Name="LayoutRoot" Background="White">
<Button Width="50" Height="50" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<TextBox Text="MyText" Width="200" Height="100" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<Canvas Background="Red" x:Name="MyCanvas" />
</Grid>
with the following code behind:
public MainPage()
{
InitializeComponent();
MyCanvas.AddHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(handler), true);
}
void handler(object sender, MouseButtonEventArgs e)
{
var point = new Point(e.GetPosition(this).X, e.GetPosition(this).Y);
var elements = VisualTreeHelper.FindElementsInHostCoordinates(point, this);
foreach (var uiElement in elements)
{
if (uiElement is TextBox){
((TextBox) uiElement).Focus();
break;
}
if(uiElement is Button)
{
//do button stuff here
break;
}
}
MyCanvas.Visibility = Visibility.Collapsed;
MyCanvas.RemoveHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(handler));
}
But: In this simple example, you get at about 20 hit elements. But they are sorted in the correct "z-Index". So you can iterate through it and the first interesting element for you is where you could break(Maybe you can do this with LINQ, too). So for me, I know that the first hit TextBox is what I want to focus.
Is this what you need?
BR,
TJ

Hiding/showing child controls when parent control gains/loses focus

I am creating a text editing control that contains a RichTextArea and a toolbar for formatting. I want the toolbar to only be visible when the control has focus. However I am finding this difficult to pull off in Silverlight, as Silverlight's focus API is very limited.
The control is structured like this:
<UserControl x:Class="MyRichTextBoxControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel x:Name="_formattingToolBar" Grid.Row="0" Orientation="Horizontal">
<Button Content="Bold" ... />
<!-- other buttons -->
</StackPanel>
<RichTextBox x:Name="_richTextBox" Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
</Grid>
</UserControl>
Initial Stab
At first I tried the obvious, I overrode OnGotFocus and OnLostFocus on the parent UserControl and hid/showed _formattingToolbar appropriately. This does not work, because if a child control gains focus, then Silverlight considers the parent control lost focus. The net result is trying to click on the toolbar causes it to disappear.
Nasty Solution
So far the only solution I have found is to hook up event handlers to the GotFocus and LostFocus events on every single child control and the parent UserControl. The event handler will call FocusManager.GetFocusedElement() and if the returned element is found to be a child of my UserControl, then keep _formattingToolbar visible, otherwise collapse it. I'm sure this will work, but it's pretty ugly.
It's also possible this idea is faulty because GotFocus/LostFocus are fired asynchronously, while GetFocusedElement() is determined synchronously. Could there be race conditions causing my idea to fail?
Anyone know of a better solution?
Nitin Midha, you had the right idea. That brought my back to my original attempt and a slight altering of OnLostFocus does the trick:
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
if (!IsChild(FocusManager.GetFocusedElement()))
{
HideToolbar();
}
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
object focusedElement = FocusManager.GetFocusedElement();
if (focusedElement is UIElement)
{
if (!this.LayoutRoot.Children.Contains((UIElement)focusedElement))
{
// Do your thing.
}
}
else { /**/ }
}

Why I cannot drop files from explorer to FlowDocumentReader and how to fix it?

I'm trying to implement a piece of functionality that will let the user to drag files into an application to be opened in the FlowDocumentReader.
My problem is that is though I have AllowDrop=true on the FlowDocumentReader, the cursor does not change to the "drop here" icon but changes instead to "drop is not allowed" icon.
This happens only to the FlowDocumentReader, all other parts og the UI (window itself, other controls) work as expected. The FlowDocumentReader actually receives the events, and it is possible to handle the drop, but the user does not have a visual indication that he can release the mouse here.
I also cannot hide the "drop is not allowed" cursor by setting Cursor=Cursors.None
Need to handle DragOver event in FlowDocument to allow dropping here.
xaml:
<!--
<FlowDocumentReader x:Name="fdr" Background="White">
<FlowDocument x:Name="doc" AllowDrop="True" DragEnter="doc_DragOver" Drop="doc_Drop" Background="White"/>
</FlowDocumentReader>
-->
<FlowDocumentReader x:Name="fdr" Background="White">
<FlowDocument x:Name="doc" AllowDrop="True" DragOver="doc_DragOver" Drop="doc_Drop" Background="White"/>
</FlowDocumentReader>
code behind:
private void doc_DragOver(object sender, DragEventArgs e)
{
e.Effects = DragDropEffects.All;
e.Handled = true;
}
private void doc_Drop(object sender, DragEventArgs e)
{
}
I couldn't find any direct way to solve this, so here is what I have ended up with:
I placed a grid on top of the FlowDocumentReader. This grid has a sold color, opacity of 0 (transparent) and Visibility=Collapsed. The purpose of this grid is to serve as a drop target.
When FlowDocument within the FlowDocumentReader received the DragEnter event, I switch the grid's visibility to Visible. The grid starts receiving drag events and the cursor stays in the "drop here" form.
When grid receives Drop or DragLeave events, its visibility is turned back to Collapsed to allow the FlowDocument receive mouse events
<FlowDocumentReader x:Name="fdr" Grid.Row="1" Background="White">
<FlowDocument x:Name="doc" DragEnter="doc_DragEnter" Background="White"/>
</FlowDocumentReader>
<Grid x:Name="dtg" Grid.Row="1" Background="White" Opacity="0"
Drop="dtg_Drop" DragLeave="dtg_DragLeave" Visibility="Collapsed"/>

Resources