WPF Zoom but keeping some items width, height and position - wpf

What I want to get is any way for making zoom on a collection of controls but keep the width, height and position for a subset of this controls. I have seen the question How to keep element size while WPF zoom in and out?, seems very much to what I want, but is not answered and also is not very explicit so a will improve the question.
Currently I'm using the Zoombox control that comes with the WPF Toolkit extended for .net framework 4.0, but I can change it. The structure that I have is the following:
<Border x:Name="drawRegionBorder" Grid.Column="1" Grid.Row="1" d:LayoutOverrides="Width, Height" BorderThickness="1" CornerRadius="4" BorderBrush="{StaticResource BorderBrush}" >
<xctk:Zoombox x:Name="zoomBox">
<Grid x:Name="drawRegion" Height="{Binding Height}" Width="{Binding Width}" HorizontalAlignment="Left" VerticalAlignment="Top" Background="{DynamicResource DrawBackgroundBrush}">
<Image Source="{Binding Image}" ... />
<ListBox x:Name="points" ItemsSource="{Binding Points}">
<ListBox.Template>
<ControlTemplate>
<Canvas IsItemsHost="True"/>
</ControlTemplate>
</ListBox.Template>
</ListBox>
<ListBox x:Name="paths" ItemsSource="{Binding SomePaths}">
<ListBox.Template>
<ControlTemplate>
<Canvas IsItemsHost="True"/>
</ControlTemplate>
</ListBox.Template>
</ListBox>
<!--... Others ...-->
</Grid>
</xctk:Zoombox>
</Border>
What I have here, are several list boxes, inside a grid, and the items panel for each list box is a Canvas, so each child (but the image) will be located inside a canvas and also each child will set the Canvas.X and Canvas.Y properties. So what I want, is any way of make the zoom (zoom-in or zoom-out), and keep the size of poitns (ellipsed) or paths...
Due the zoom, is a wpf's scale transformation, I suppose maybe a way for doing this when the zoom-in, make the zoom-out to the control I want to keep the size, and viceversa.
An example of the spected behavior is the blend designer, for instance when you zoom in a grid with rows and columns, the columns indicators keeps the original size, some thing like that is what I want for my points and paths.
I will appreciate any solution, maybe library, attached property, behavior or code.
Thaknks

The Blend designer uses Adorners for its manipulators. The sizes are calculated according to zoom the current zoom. If you're interested in using a similar technique, it's actually not that hard to do the calculation yourself.
You would create an adorner set to the Bounds of your control, then apply a scale factor according to the zoom in the designer. So if you zoom by 2.0, then you apply a RenderTransform of Scale 2.0 to your zoomed control, while calculating your adorner to be 2.0 of the ActualWidth and ActualHeight of the zoomed control (because those two properties do not take into account RenderTransformations). The nice thing is that since everything uses doubles, you get pixel perfect precision when doing this kind of calculation.
By using this approach, you gain the ability to zoom your main controls, while your manipulators simply scale to the zoomed controls, but maintain their control size throughout.
This tutorial is a good starting point. In the OnRender method is where you'd want to apply the scaling factor (by calculating the bounds of your adorner based on the UIElement's ActualWidth/Height, then multiplying by your scaling factor). How you apply your adorner depends a lot on application context - if you're doing a designer, then you'd want to apply the adorners in a design canvas or upon item selection.

I had made a research and found some useful things, for instace, if you are working with Adorners you can override the GetDesiredTransform in order to set what transform to do you want your adorner to do, here you can make null the transform made to your adorner. For more details see How to exclude scaleTransform from GeneralTransform in Adorner GetDesiredTransform method. In Msdn. But I think that if I want make it on controls I need to control the transform by my self.

Related

WPF borders and the controls within them

This post is about the controls contained within a WPF Border control. It's also about having a border that can appear and disappear without affecting the contained controls.
For the record, I'm using C# and WPF and most of the view stuff is using XAML. I also use MVVM although I'm not sure that's going to be related.
What I had planned for was a border around a control that I could make appear and disappear, for the effect of a highlight or something like that. But when I change certain properties of the Border, for example the Opacity or Visiblity, they impact on the contained controls. I have also tried changing the Background property to Transparent and that has not made a difference.
I do know that some controls have a Border property, but that's not really the case for my situation.
How can I do this?
Thanks
Try this:
<Grid>
<Border BorderThickness="2">
<YourControl />
</Border>
<Border Opacity="0.5" BorderBrush="Red" BorderThickness="2" />
</Grid>
This way you can change the opacity of the second border without affecting your control. The trick is that Grid ensures that both elements inside it have the same dimensions.
Also notice how your control is wrapped in another border with the same thickness but with no brush. This is to keep the second border from obscuring your control.

WPF ContentControl width grows but doesn't shrink when wrapped in a ScrollViewer

I'm trying to figure out how to make my ContentControl to correctly scroll Horizontally (Vertically its fine at the moment). By correctly i mean i would like to see the content to stretch (expand infinitely) while having minimum sizes to which a scrollbar would appear in order for the content not to overflow behind the ContentControl's area, so here's a quick introduction:
The main window is structured in this way:
Grid (2 columns of .3* and .7*)
Border
Grid (7 rows, one set to * where ContentControl is)
ScrollViewer with StackPanel (purely for test) wrapping a ContentControl that has Auto Width
ContentControl's Template:
Grid (Width set to UserControl's ActualWidth, 6 rows with one set to Auto where ItemsControl go
ItemsControl that describes an ItemTemplate of a type DataTemplate which contains a Grid inside of which i have a DataGrid
The actual problem is that the ContentControl grows as you resize the window, but does not shrink with window resize.
Main View XAML (truncated for clarity):
<ScrollViewer Grid.Row="5" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<ContentControl Grid.Row="5" Background="Transparent" Focusable="False" Margin="0,5,0,0"
Content="{Binding CurrentSection}" ContentTemplateSelector="{StaticResource templateSelector}/>
</ScrollViewer>
Tempate XAML (truncated for clarity):
<Grid>
...
<ItemsControl Grid.Row="4" ItemsSource="{Binding Data.QualifyingDistributionsDividends}" x:Name="QualifyingItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="DTLayoutGrid">
...
<Grid Grid.Row="1" x:Name="DataLayout" Width="{Binding ElementName=DTLayoutGrid, Path=ActualWidth}" HorizontalAlignment="Stretch">
...
<DataGrid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="8" HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Payments}" Style="{StaticResource DataGridStyle}" CellStyle="{StaticResource DataGridNormalCellStyle}">
</DataGrid>
</Grid>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
So what happens? Datagrid assumes width of the entire DataTemplate (well its underlying controls that are set to be DataTemplates size, then * column assumes all empty space. When you try to resize the entire window that holds this code it will grow correctly, expanding the * column but it seems shrinking is not "registered" and it keeps the size you expanded it to, applies a scrollbar over that and forgets about it.
What i've tried so far was to set widths for ItemsControl, its underlying parents like Grid etc, also setting size to ContentControl, StackPanel, ScrollViewer and parent Grid of that.
I've also tried using scrollviewers directly on the Datagrid which produces an epileptic "1 million resizes a second" scenario. I've also played around with HorizontalAlignments
Under certain situations i DID managed to get the horizontal scrollbar to appear correctly but unfortunately that makes my DataGrid's * column to assume Auto Width rather then Star so DataGrid starts having an empty area to the right (unacceptable unfortunately...)
I understand that in order for horizontal scrollbar to work the parent or child of the scrollviewer needs Width set, i guess i can't work out where exactly do i need to restrict it. DataGrids NEED to infinitely expand with the main window while having first column fill all the available space.
Do let me now if you need more information on this and I will gladly answer.
It seems to me that this is just another case of the dreaded StackPanel layout problem. This problem comes up again and again and I confess that I had the very same problem when I started learning WPF. The StackPanel does not take the available size of its parent into consideration whereas other Panels such as a DockPanel or a Grid (yes, that's actually a Panel too) do.
It's explained in the How to: Choose Between StackPanel and DockPanel page on MSDN:
Although you can use either DockPanel or StackPanel to stack child elements, the two controls do not always produce the same results. For example, the order that you place child elements can affect the size of child elements in a DockPanel but not in a StackPanel. This different behavior occurs because StackPanel measures in the direction of stacking at Double.PositiveInfinity; however, DockPanel measures only the available size.
The StackPanel should only really be used to align a number of items, such as Buttons or other controls in a straight line where available space is not a concern. So anyway, the solution should be simple... just remove the StackPanel from the ScrollViewer. It doesn't appear to serve any purpose there anyway.
UPDATE >>>
After looking again, it seems as though you're saying that the problem is inside the DataTemplate, right? You might be able to fix that by setting the ItemsControl.HorizontalContentAlignment property to Stretch. That would ensure that each item remains within the boundary of the ItemsControl.
I'd also remove your Binding on the Grid.Width as you don't need it... a child Grid will take up the full space of a parent Grid by default. If these ideas don't work, just simplify your problem. Seriously, if you follow the advise in the linked page from the Help Center that I gave you in the comments, then you'll either fix the problem, or be able to come back here and provide a complete, but concise example that we could test.
I've found the behavior I was looking for by using a UniformGrid as the ItemsPanel, with its rows bound to the count of the ItemsSource model:
<ScrollViewer>
<ItemsControl ItemsSource="{Binding MyCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding MyCollection.Count}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
As #Sheridan pointed out above, it seems the StackPanel is causing trouble. Also, credit to https://stackoverflow.com/a/23375262/385273 for pointing out the UniformGrid option.

How to use ScrollViewer.ScrollToVerticalOffset?

I hope this isn't a duplicate but I can't find any documentation or examples on how to actually use ScrollToVerticalOffset(). I'm using it in a Windows Phone 8 app, but I think it will still apply to WP7 and Silverlight (although, feel free to correct me if I'm wrong).
So here is my basic set up (pseudo-code from memory):
<phone.PivotItem>
<ScrollViewer>
<Grid Height="1500">
<Grid.RowDefinitions>
<!-- about 20 rows, all auto-height -->
</Grid.RowDefinitions>
<Border Grid.Row="0">
<TextBox x:Name="txt1" />
</Border>
<Border Grid.Row="1">
<TextBox x:Name="txt2" />
</Border>
<!-- ...... -->
<Border Grid.Row="19">
<TextBox x:Name="txt20" />
</Border>
</Grid>
</ScrollViewer>
</phone.PivotItem>
So as you can see, I've got a ScrollViewer within a PivotItem, and inside is a Grid. In the Grid there are about 20 TextBoxs, each within a Border. I am dynamically setting focus to one of these TextBoxs when this page loads, so anytime I set focus to TextBox #6-20 (roughly) - I have to manually scroll down to see it. I want to auto-scroll my ScrollViewer so that whichever TextBox has focus, it will be centered for the user to see.
The documentation for ScrollToVerticalOffset() says:
Scrolls the content that is within the ScrollViewer to the specified
vertical offset position.
And that it accepts a type of System.Double.
What I don't understand is A) the value I'm supposed to pass, and B) how I could even get that value? Is it supposed to be a number between 0 and the height of my Grid (1500)? If so, how could I determine the position of any given TextBox so I can scroll to it?
If there are any straightforward examples, please feel free to link out to them. I'm not sure if the content within the ScrollViewer matters when calling this method, but in case it does I wanted to show exactly how I'm using it.
Many thanks in advance!
You can see any UIElement's position relative to another UIElement using the UIElement.TransformToVisual call.
First, get the transform between the TextBox and ScrollViewer.
GeneralTransform transform = textBox.TransformToVisual(scrollViewer);
Then, figure out what point (0,0) of the TextBox is relative to the ScrollViewer. Meaning, the TextBox origin (0,0) is located at what ScrollViewer position.
Point textBoxPosition = transform.Transform(new Point(0, 0));
Now that you know the Y position of the TextBox relative to the ScrollViewer, scroll to that Y offset.
scrollViewer.ScrollToVerticalOffset(textBoxPosition.Y);
Good luck!
This is a very old post, but the meaning of VerticalOffset varies.
Most of the solutions I have seen assume VeritcalOffset is in pixels. This is not always the case.
From: https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.scrollviewer.extentheight
If CanContentScroll is true, the values of the ExtentHeight, ScrollableHeight,
ViewportHeight, and VerticalOffset properties are number of items. If
CanContentScroll is false, the values of these properties are Device Independent Pixels.

In XAML how to say: default width and height for e.g. TextBox

So I'm coming at WPF from a HTML perspective.
I just want to put a TextBox on my Window like this:
<Grid>
<TextBox Name="theName" />
</Grid>
Turns out that the TextBox is then HUGE, covers the whole window. (!)
Ok, that's not what I want, but I don't want to define the EXACT size either since I know Height and Width should be flexible, so I try:
<TextBox Name="theName" Width="Auto" Height="Auto"/>
Same thing. So I try:
<TextBox Name="theName"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
Same thing. So I just hard code the sizes:
<TextBox Name="theName" Width="100" Height="20"/>
Which I know is not a good programming practice in WPF.
So, what how do you tell TextBox to "display default sizes for the font size being used"?
You can take Bryan's example even a bit further. By specifying a specific alignment that isn't stretch and further constrain the TextBox so that it won't expand beyond a certain size. eg:
<Grid x:Name="LayoutRoot">
<TextBox HorizontalAlignment="Left" VerticalAlignment="Top" Text="TextBox" TextWrapping="Wrap"
MinWidth="15" MinHeight="20" MaxWidth="500" MaxHeight="50"/>
</Grid>
You can take it even further by setting up rows/columns inside the Grid and constraining them in various fashions. As you're coming from an HTML background, think of it like using a table to control layout. Remember that you can also nest other container objects (i.e. StackPanels, WrapPanels, other Grids, etc...).
The challenge with XAML and the WPF/Silverlight controls is that they a very flexible, so you've got to get a handle on all the options and how they affect layout.
Good luck. I'm going through this exact same thing now.
Use a different container.
The Grid always streches its child controls to fill the grid cell.
You could use e.g. a stackpanel which only streches its controls in one direction.
In addition to using a different panel as Stefan mentioned you could just give the TextBox an alignment that isn't Stretch. e.g.
<TextBox Name="theName" HorizontalAlignment="Left" VerticalAlignment="Top"/>
The sizes in WPF aren't pixels, they are "device independent pixels" that are 1/96 of an inch - so in today's normal DPI setup they map 1:1 to pixels.
But, if you run the program in high DPI mode the TextBox will grow with the DPI (and the font).
So setting an hard-coded size isn't that bad.
Other than that you can only use HorizontalAlignment and VerticalAlignment that are not "Stretch", this will size the TextBox to content - but then an empty TextBox will be tiny.
You can set VerticalAlignment to "Center", "Top" or "Bottom" to get automatic height of about one line (maybe backed up by a MinHeight setting to avoid problems really tiny fonts) and then set the Width so the TextBox width does not change as the user types into it.

What's the best way to display a video with rounded corners in Silverlight?

The MediaElement doesn't support rounded corners (radiusx, radiusy). Should I use a VideoBrush on a Rectangle with rounded corners?
Yeah - In a way you're both asking and answering the question yourself... But that is one of the two options I can think of. The reasons that might be a problem is that you lose some of the features/control you get from the MediaElement control. Another option is to do this:
Add your MediaElement to your page.
Draw a Rectangle on top of it and set wanted corner radius
Right click the rectangle in Blend and choose "Create Clipping Path"
Apply the clipping path to your MediaElement
That way you're still using a MediaElement control, but you can "clip" away what ever you want to get the desired rounded effect.
This example shows a clipped MediaElement. I know it's not easy to picture the vector path, but if you open it open in Blend you will see a rounded MediaElement.
<MediaElement
Height="132" Width="176" Source="Egypt2007.wmv"
Clip="M0.5,24.5 C0.5,11.245166 11.245166,0.5 24.5,0.5 L151.5,0.5
C164.75484,0.5 175.5,11.245166 175.5,24.5 L175.5,107.5 C175.5,
120.75484 164.75484,131.5 151.5,131.5 L24.5,131.5 C11.245166,
131.5 0.5,120.75484 0.5,107.5 z"/>
Using a rounded rectangle and a VideoBrush doesn't lose you any features/control over using a displayed MediaElement - since the element has to be in the Xaml anyway, you can control it using the usual Play/Pause/Stop methods, except that the playback happens in your rectangle. Using a clip region is a little unwieldy because it's harder to resize the region. A Rectangle is better because you have flexibility of layout.
<MediaElement x:Name="myElement" Source="clip.wmv" Visibility="Collapsed"/>
<Rectangle RadiusX="10" RadiusY="10" Width="640" Height="480">
<Rectangle.Fill>
<VideoBrush Source="myElement" Stretch="Uniform"/>
</Rectangle.Fill>
<Rectangle/>
The clip path with give you "hard" edges - you could also use an OpacityMask as well (though I imagine this requires much more processing power).
Try this
<Border CornerRadius="8" BorderBrush="Black" Background="Black" BorderThickness="3">
<MediaElement HorizontalAlignment="Center" VerticalAlignment="Top" Stretch="Fill" x:Name="Player" Source="/Assets/Videos/x.mp3" />
</Border>

Resources