Creating a WPF Window that allows zooming and panning - wpf

I want to create a Window that will hold several controls. However, I would like the user to be able to pan around and zoom in and out to see larger versions of those controls.
I don't even know where to begin looking.
I was going to start at ScaleTransform that responds to the use of the scroll button on the mouse but I am not sure if that is the best idea.
Just need a push in the right direction.
thanks!

This might be a good candidate for a Viewbox.
See here: http://msdn.microsoft.com/en-us/library/system.windows.controls.viewbox(v=vs.110).aspx
Basically, you can Wrap the entire contents of the Window into a Viewbox like so:
<Window>
<Viewbox>
<!-- content here -->
</Viewbox>
</Window>
and then bind to the Viewbox control's width and height to simulate the zooming. For a quick test, you could just listen to scroll wheel events via code-behind, name the Viewbox control, and access the Viewbox directly at change the values there.
Edit: here's a scenario I just found to get you started. They are using an image, but it's the exact same concept that I described above.
http://www.c-sharpcorner.com/uploadfile/yougerthen/working-with-wpf-viewbox-control/
Edit2: Quick working example using mouse-scroll
Xaml:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
MouseWheel="MainWindow_OnMouseWheel">
<Grid>
<Viewbox x:Name="ZoomViewbox" Stretch="Fill">
<StackPanel>
<Label Content="Label" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" />
</StackPanel>
</Viewbox>
</Grid>
</Window>
C#:
using System.Windows;
using System.Windows.Input;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ZoomViewbox.Width = 100;
ZoomViewbox.Height = 100;
}
private void MainWindow_OnMouseWheel(object sender, MouseWheelEventArgs e)
{
UpdateViewBox((e.Delta > 0) ? 5 : -5);
}
private void UpdateViewBox(int newValue)
{
if ((ZoomViewbox.Width >= 0) && ZoomViewbox.Height >= 0)
{
ZoomViewbox.Width += newValue;
ZoomViewbox.Height += newValue;
}
}
}
}

You can get functionality out of a ScrollViewer and a ScaleTransform. Here's an example:
<Window x:Class="CSharpWpf.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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- This ScrollViewer enables the panning -->
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<!-- This StackPanel is the container for the zoomable/pannable content. -->
<!-- Any container control (StackPanel, DockPanel, Grid, etc) may be used here. -->
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top">
<!-- This ScaleTransform implements the zooming and is bound the Value of the ZoomSlider -->
<StackPanel.LayoutTransform>
<ScaleTransform ScaleX="{Binding ElementName=ZoomSlider, Path=Value}" ScaleY="{Binding ElementName=ZoomSlider, Path=Value}" />
</StackPanel.LayoutTransform>
<!-- Here is your custom content -->
<Button>Foo</Button>
<Button>Bar</Button>
</StackPanel>
</ScrollViewer>
<!-- This Slider controls the zoom level -->
<Slider x:Name="ZoomSlider" Orientation="Horizontal" Grid.Row="1" Minimum="0.0" Maximum="8.0" LargeChange="0.25" SmallChange="0.01" Value="1.0" />
</Grid>
</Window>

Simple solution :
private void Window_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (Keyboard.Modifiers != ModifierKeys.Control)
return;
if (e.Delta < 0 && _scale > 0.7)
{
_scale -= 0.1;
MainGrid.LayoutTransform = new ScaleTransform(_scale, _scale);
}
else if (e.Delta > 0 && _scale < 1.5)
{
_scale += 0.1;
MainGrid.LayoutTransform = new ScaleTransform(_scale, _scale);
}
}

Related

Right align button in WPF control to visible area side (ignore scroll)

I have a user control, with the following (simlified) layout:
<UserControl x:Name="FV">
<DockPanel LastChildFill="False">
<StackPanel DockPanel.Dock="Left">
... some content, let's say customer name ...
</StackPanel>
<MyButton DockPanel.Dock="Left"
Visibility="{Binding Path=IsMouseOver, ElementName=FV, Converter={StaticResource boolToVisibilityConverter}}">
</MyButton>
</DockPanel>
</UserControl>
So basically it shows a text and on hover an edit button just to the right of this text.
Now I use this control as ItemTemplate in a tree.
The problem comes with long names. In this case the tree gets a horizontal scroll and UserControl logically stretches to the right and my button is not visible anymore.
text1 (edit) |
text22 (edit) |
vverylongtext |
I want to overlap the verylongtext on hover with my button:
text1 (edit) |
text22 (edit) |
vverylo(edit) |
How can I achieve this? My UserControl has no knowledge of where it is used and thus no knowledge of ActualWidth of parent elements.
I did it exactly according to your requirement. I created a UserControl with a TextBlock and Button. If text in TextBlock is very long, Button remains out of sight, which upon MouseOver comes int sight exactly as you need. However, if text in TextBlock remains small enough, Button remains in sight.
Note : HorizontalAlignment = Left must be set on the Button.
Window3.xaml
<Window x:Class="WpfStackOverflow.Window3"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:WpfStackOverflow"
Title="Window3" Height="300" Width="300" SizeToContent="WidthAndHeight">
<StackPanel>
<uc:UserControl1 Width="200" Height="35"/>
</StackPanel>
</Window>
UserControl.1.xaml
<UserControl x:Class="WpfStackOverflow.UserControl1"
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"
xmlns:local="clr-namespace:WpfStackOverflow"
Background="Bisque"
Height="25">
<StackPanel x:Name="DckPnl" Height="25" Orientation="Horizontal">
<TextBlock x:Name="Tb" MouseEnter="Tb_MouseEnter_1" MouseLeave="Tb_MouseLeave_1" FontFamily="Arial" Text="some content , let's say customer name some content, let's say customer name" Background="AliceBlue"/>
<Button x:Name="Btn" Visibility="Hidden" Content="Edit" Width="35" Height="25" Margin="0 0 0 0" HorizontalAlignment="Left"/>
</StackPanel>
</UserControl>
UserControl1.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace WpfStackOverflow
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
private void Tb_MouseEnter_1(object sender, MouseEventArgs e)
{
Thickness newMargin = new Thickness();
FormattedText f = new FormattedText(Tb.Text,
new System.Globalization.CultureInfo("en-US"),
System.Windows.FlowDirection.LeftToRight,
new Typeface("Arial"),
Tb.FontSize, Brushes.Black);
if (f.Width > this.ActualWidth)
newMargin = new Thickness((this.ActualWidth - f.Width) - Btn.ActualWidth, 0, 0, 0);
else
newMargin = Btn.Margin;
Btn.Margin = newMargin;
Btn.Visibility = System.Windows.Visibility.Visible;
}
private void Tb_MouseLeave_1(object sender, MouseEventArgs e)
{
Btn.Margin = new Thickness(0, 0, 0, 0);
Btn.Visibility = System.Windows.Visibility.Hidden;
}
}
}
To place your button over the text you can use AdornerLayer, Z-Index, Tooltip or Popup. The last seems to me the easiest solution. Here is an example of how it can be done with popup:
<StackPanel Margin="0 20 0 0" Name="StackPanel" Width="100">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ScrollViewer.Resources>
<wpfApplication1:OrConverter x:Key="OrConverter" />
</ScrollViewer.Resources>
<StackPanel Orientation="Horizontal">
<TextBlock Text="longlonglonglongtextlonglonglonglongtextlonglonglonglongtext" />
<Popup x:Name="Popup" StaysOpen="True"
PlacementTarget="{Binding ElementName=StackPanel}"
Placement="Right"
HorizontalOffset="-20"> <!--here you can bind to button's width instead of static value-->
<Popup.IsOpen>
<MultiBinding Converter="{StaticResource OrConverter}">
<Binding ElementName="StackPanel" Path="IsMouseOver" Mode="OneWay" />
<Binding ElementName="Popup" Path="IsMouseOver" Mode="OneWay" />
</MultiBinding>
</Popup.IsOpen>
<Button Name="Button" Content="X" Height="16" Width="20" VerticalAlignment="Top" />
</Popup>
</StackPanel>
</ScrollViewer>
</StackPanel>
OrConverter can be found in this answer.
And it looks like this.

OOB Silverlight and content scale on window resize

Good morning,
I faced problem when doing scaleTransform on the content when OOB Silverlight application's window gets resized. The code looks very simple
xaml
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="Aquamarine" RenderTransformOrigin="0,0">
<Rectangle Fill="Aqua" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<Grid VerticalAlignment="Center" HorizontalAlignment="Center" Height="150">
<TextBlock FontSize="20" Text="Hello World" Margin="5" VerticalAlignment="Top" HorizontalAlignment="Center"/>
<Button Content="Button" VerticalAlignment="Bottom" HorizontalAlignment="Center"/>
</Grid>
</Grid>
</UserControl>
C#
public MainPage()
{
InitializeComponent();
SizeChanged += MainPage_SizeChanged;
}
void MainPage_SizeChanged(object sender, SizeChangedEventArgs e)
{
ScaleTransform scaleTransform = new ScaleTransform();
scaleTransform.ScaleX = (Application.Current.MainWindow.Width / 500f);
scaleTransform.ScaleY = (Application.Current.MainWindow.Height / 500f);
scaleTransform.CenterX = 0;
scaleTransform.CenterY = 0;
// this.RenderTransform doesn't work either.
LayoutRoot.RenderTransform = scaleTransform;
}
The content gets scaled when the main window is resized, but, for some reason, I see white background of the window when scaling down. It seems that the content is scaled down faster than window and I don't know what I am doing wrong. The code seems logical and should work, shouldn't it?
The LayoutTransformer saved my day. I have changed the code and xaml as follows and everything works now. Code may need a little tweaking though.
xaml
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:layout="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Layout.Toolkit"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="500">
<layout:LayoutTransformer x:Name="LayoutTransformer">
<layout:LayoutTransformer.Content>
<Grid x:Name="LayoutRoot" Background="AntiqueWhite">
<Rectangle Fill="Aqua" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<Grid VerticalAlignment="Center" HorizontalAlignment="Center" Height="150">
<TextBlock FontSize="20" Text="Hello World" Margin="5" VerticalAlignment="Top" HorizontalAlignment="Center"/>
<Button Content="Button" VerticalAlignment="Bottom" HorizontalAlignment="Center"/>
</Grid>
</Grid>
</layout:LayoutTransformer.Content>
<layout:LayoutTransformer.LayoutTransform>
<ScaleTransform x:Name="ContentScale" CenterX="0" CenterY="0" ScaleX="1" ScaleY="1" />
</layout:LayoutTransformer.LayoutTransform>
</layout:LayoutTransformer>
</UserControl>
C#
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
SizeChanged += MainPage_SizeChanged;
}
void MainPage_SizeChanged(object sender, SizeChangedEventArgs e)
{
double yScale = ActualHeight / 500f;
double xScale = ActualWidth / 500f;
double value = GetScale(xScale, yScale);
ContentScale.ScaleX = value;
ContentScale.ScaleY = value;
LayoutTransformer.ApplyLayoutTransform();
}
private double GetScale(double xScale, double yScale)
{
return Math.Max(0.1, Math.Min(xScale, yScale));
}
}

Use a UserControl inside a Ribbon

I want to use a UserControl inside a RibbonControl.
For example:
<RibbonWindow
xmlns:uc="clr-namespace:UserControl1;assembly=UserControl1">
<Grid ShowGridLines="False" Margin="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="100*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100*"/>
</Grid.ColumnDefinitions>
<Ribbon Grid.Row="0">
<RibbonApplicationMenu >
<RibbonApplicationMenuItem x:Name="miExit" ImageSource="Images/large_exit.png" Header="Exit" />
</RibbonApplicationMenu>
<RibbonTab Header="Test">
<uc:RibbonGroups/>
</RibbonTab>
</Ribbon>
<uc:Content Grid.Row="1" />
</Grid>
</RibbonWindow>
You see two UserControls, uc:RibbonGroups and uc:Content. While the second one (uc:Content) works fine, the UserControl inside the RibbonControl won't work.
What i missed?
I also try this approach:
How to set the usercontrol for ribbon window in WPF?
an set a grid inside the RibbonTab and then the UserControl;
<Ribbon Grid.Row="0">
...
<Grid>
<RibbonTab Header="Test">
<uc:RibbonGroups/>
</RibbonTab>
</Grid>
</Ribbon>
instead of
<Ribbon Grid.Row="0">
...
<RibbonTab Header="Test">
<uc:RibbonGroups/>
</RibbonTab>
</Ribbon>
Edit:
UserControl now only holds the content of RibbonTab, RibbonTab itself moved to the main window
<UserControl ...>
<StackPanel Orientation="Horizontal">
<RibbonGroup Header="Test" >
<RibbonButton SmallImageSource="Images/small_save.png"
LargeImageSource="Images/large_save.png" />
<Image Source="Images/large_expert_enabled.png"></Image>
</Stackpanel>
</UserControl>
RibbonGroup are shown with the Header but without the images, and the separate Image are shown itself. So maybe there are now any suggestions what goes wrong?
EDIT2:
If i step into with Snoop, the RibbonButtons and their corresponding images are present.
EDIT3:
I tried another way with resources, but same problem as above..
How it looks, when i have the RibbonTab in the resource/UserControl:
http://s12.postimg.org/lpposl48t/DLL_Ribbon_Tab_fail_part.png
How it looks, when i have the RibbonGroup in the resource/UserControl:
http://s12.postimg.org/oiiwcm4l9/DLL_Ribbon_Group_fail_part.png
After a lot of research i found this from Ben Barefield:
http://www.benbarefield.com/blog/2011/03/04/ribbon-tab-definition-on-usercontrol/
He use a trick and move the ribbontab into a resourcedictionary.
Works well, but i also wanted to add some click events to this ribbontab (with view specific actions....) so i decided to fall back to the usercontrol.
The trick is, to detach the ribbontab from the usercontrol before adding it to the other control.
In detail:
add this methods to the usercontrol codebehind:
private void AttachTabToRibbon(RibbonTab tab)
{
Ribbon Ribbon = null;
Ribbon = getRibbon();
if (Ribbon == null)
return;
Ribbon.Items.Add(tab);
}
private Ribbon getRibbon()
{
var control = VisualTreeHelper.GetParent(this);
Ribbon Ribbon = null;
while (control != null && Ribbon == null)
{
var numberOfChildren = VisualTreeHelper.GetChildrenCount(control);
for (int i = 0; i < numberOfChildren; ++i)
{
var child = VisualTreeHelper.GetChild(control, i);
if (child is Ribbon)
{
Ribbon = child as Ribbon;
return Ribbon;
}
}
control = VisualTreeHelper.GetParent(control);
}
return null;
}
private void userControl_Loaded(object sender, RoutedEventArgs e)
{
Ribbon ribbonMenu = (Ribbon)this.FindName("ribbon");
RibbonTab ribbonTabAdminInterface = (RibbonTab)this.FindName("ribbonTab");
ribbonMenu.Items.Remove(ribbonTab);
ucStackPanel.Children.Remove(ribbonMenu);
AttachTabToRibbon(ribbonTab);
}
furthermore call the userControl_Loaded:
<UserControl ...
Loaded="userControl_Loaded"
>
<StackPanel x:Name="ucStackPanel">
<Ribbon x:Name="ribbon" >
<Ribbon.ApplicationMenu>
<RibbonApplicationMenu
</RibbonApplicationMenu>
</Ribbon.ApplicationMenu>
<RibbonTab x:Name="ribbonTab"/>
...
</Ribbon>
...
</StackPanel>

Specify column added order in a WPF Grid in code behind?

Adding a column to a grid in code behind is easy:
col10 = new ColumnDefinition();
col10.SharedSizeGroup = "column1";
When you add the column it adds to the end of the grid for example you have a grid with columns A and B, you use the code above and a new column (C) and it is added as A B C.
Is it possible to set it up like this?
C A B
Instead on adding to the end its added to the front?
Thanks
ColumnDefinitions are like any other Collection and support the IList<> interface.
So just use an insert method to control added order.
ColumnDefinition myColumn = new ColumnDefintion();
Grid myGrid = new Grid();
myGrid.ColumnDefinitions.Insert(0, myColumn);
Try this:
XAML file:
<Window x:Class="DataGridAddColumn.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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<ComboBox Name="cbWhere" Width="100" VerticalAlignment="Center">
<ComboBoxItem>Front</ComboBoxItem>
<ComboBoxItem>End</ComboBoxItem>
</ComboBox>
<TextBlock Text="Name:" VerticalAlignment="Center" Margin="10,0,0,0" />
<TextBox Name="tbName" MinWidth="100" VerticalAlignment="Center" />
<Button Content="Create" VerticalAlignment="Center" Margin="10,0,0,0" Click="Button_Click" />
</StackPanel>
<DataGrid Grid.Row="1" Name="grid" />
</Grid>
</Window>
Code-behind:
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace DataGridAddColumn
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void AddColumn(DataGrid grid, string name, int where)
{
if (where == 0)
{
grid.Columns.Insert(0, new DataGridTextColumn{Header = name});
}
else
{
grid.Columns.Add(new DataGridTextColumn { Header = name });
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
AddColumn(grid, tbName.Text, cbWhere.SelectedIndex);
}
}
}

WPF ScrollViewer/Canvas mouse event handler

I've created the following control:
<UserControl x:Class="FooBar.AnnotationControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="400" Width="500" >
<ScrollViewer Height="400" Width="500">
<Canvas Height="400" Width="500" Name="ctlCanvas" MouseLeftButtonDown="MouseLeftButtonDownHandler" >
<Canvas.RenderTransform>
<ScaleTransform x:Name="ZoomTransform" />
</Canvas.RenderTransform>
</Canvas>
</ScrollViewer>
</UserControl>
namespace FooBar
{
public partial class AnnotationControl : UserControl
{
public AnnotationControl()
{
InitializeComponent();
}
private void MouseLeftButtonDownHandler( object sender, MouseButtonEventArgs args)
{
//Do Something
}
}
}
when I click the canvas, I don't hit breakpoints in the MouseLeftButtonDownHandler. I even attach this handler to the ScrollViewer and get the same result. Any idea what's going on here?
The default background for a Canvas is Transparent, which allows hit tests to pass through it. To make your Canvas register for HitTests, give it a Background Color.
<Canvas Background="White" ... />

Resources