I would like to make a small silverlight app which displays one fairly large image which can be zoomed in by scrolling the mouse and then panned with the mouse.
it's similar to the function in google maps and i do not want to use deepzoom.
here is what i have at the moment. please keep in mind that this is my first silverlight app:
this app is just for me to see it's a good way to build in a website. so it's a demo app and therefor has bad variable names.
the initial image is 1800px width.
private void sc_MouseWheel(object sender, MouseWheelEventArgs e)
{
var st = (ScaleTransform)plaatje.RenderTransform;
double zoom = e.Delta > 0 ? .1 : -.1;
st.ScaleX += zoom;
st.ScaleY += zoom;
}
this works, but could use some smoothing and it's positioned top left and not centered.
the panning is like this:
found it # Pan & Zoom Image
and converted it to this below to work in silverlight
Point start;
Point origin;
bool captured = false;
private void plaatje_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
plaatje.CaptureMouse();
captured = true;
var tt = (TranslateTransform)((TransformGroup)plaatje.RenderTransform)
.Children.First(tr => tr is TranslateTransform);
start = e.GetPosition(canvasje);
origin = new Point(tt.X, tt.Y);
}
private void plaatje_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
plaatje.ReleaseMouseCapture();
captured = false;
}
private void plaatje_MouseMove(object sender, MouseEventArgs e)
{
if (!captured) return;
var tt = (TranslateTransform)((TransformGroup)plaatje.RenderTransform).Children.First(tr => tr is TranslateTransform);
double xVerschuiving = start.X - e.GetPosition(canvasje).X;
double yVerschuiving = start.Y - e.GetPosition(canvasje).Y;
tt.X = origin.X - xVerschuiving;
tt.Y = origin.Y - yVerschuiving;
}
so the scaling isn't smooth and the panning isn't working, because when i click it, the image disappears.
first thing I noticed was this:
var st = (ScaleTransform)plaatje.RenderTransform;
and
(TransformGroup)plaatje.RenderTransform
. So in one handler, you're casting "plaatje.RenderTransform" to ScaleTransform, in the other you're casting to TransformGroup?
This is probably causing an exception, making your image disappear.
For the zooming part, you might want to try setting the RenderTransformOrigin of the object you want to scale ("plaatje"?) to "0.5,0.5", meaning the center of the UI element. So the image will be scaled around its center point instead of its top left corner.
Cheers, Alex
Related
I have a question regarding WPF.
I have a Canvas that serves as a visual editor! I have a few 'nodes' positioned in the Canvas using 'X' and 'Y' properties (Canvas.Left and Canvas.Top). Now, I need this Canvas to let the user Zoom (in & out) and Pan around, as he want's to.
I implemented kind of a hack to emulate that behavior. This is the Code that let's the user 'pan' around in the Canvas:
///In file MainWindow.xaml.cs
private void ZoomPanCanvas_MouseMove(object sender, MouseEventArgs e) {
if (IsMouseDown) {
///Change the Cursor to Scroll
if (mNetworkUI.Cursor != Cursors.ScrollAll)
mNetworkUI.Cursor = Cursors.ScrollAll;
var currPosition = e.GetPosition(mNetworkUI);
var diff = currPosition - MouseLastPosition;
var p = new Point(diff.X, diff.Y);
mNetworkUI.ViewModel.Network.SetTransformOffset(p);
MouseLastPosition = currPosition;
}
}
///In file NetworkViewModel.cs
public void SetTransformOffset(Point newOffset) {
for (int i = 0; i < Nodes.Count; i++) {
Nodes[i].X += newOffset.X;
Nodes[i].Y += newOffset.Y;
}
}
Where the 'Nodes' are my editor-nodes displayed in the Canvas. The Zooming (with respect to the mouse position works as follows:
///File MainWindow.xaml.cs
private void ZoomPanCanvas_MouseWheel(object sender, MouseWheelEventArgs e) {
///Determine the Scaling Factor and Scale the Rule-Editor
var factor = (e.Delta > 0) ? (1.1) : (1 / 1.1);
currrentScale = factor * currrentScale;
ScaleNetwork();
///Translate the Nodes to the desired Positions
var pos = e.GetPosition(mNetworkUI);
var transform = new ScaleTransform(factor, factor, pos.X, pos.Y);
var offSet = new Point(transform.Value.OffsetX, transform.Value.OffsetY);
mNetworkUI.ViewModel.Network.SetTransformOffset(offSet);
}
///Also in MainWindow.xaml.cs
private void ScaleNetwork() {
mNetworkUI.RenderTransform = new ScaleTransform(currrentScale, currrentScale);
mNetworkUI.Width = ZoomPanCanvas.ActualWidth / currrentScale;
mNetworkUI.Height = ZoomPanCanvas.ActualHeight / currrentScale;
}
So, in the 'panning' I calculate the difference to the last mouse position and use that vector to manipulate the nodes, not the Canvas itself.
When I zoom, I determine the new zoom, set a new RenderTransform, resize the Canvas to again fill the provided space and again re-position the nodes in the Canvas.
It works very well for now. I can 'pan & zoom' around how I want, but I realized, that with many nodes present in my 'network' (connected nodes), things get quite slow.
One reason is, that on every movement of a node some events are raised resulting in a noticable delay when panning.
How is such a thing (without fixed Canvas-size and Scrollbars) possible in a performant manner? Is there a control out there that I can use? Is this possible with the Extended WPF toolkit's ZoomBox control?
Thank you!
I've written a Viewport control for this exact functionality.
I've also packaged this up on nuget
PM > Install-Package Han.Wpf.ViewportControl
It extends a ContentControl which can contain any FrameworkElement and provides constrained zoom and pan functionality. Just make sure to add Generic.xaml to your app.xaml
<Application.Resources>
<ResourceDictionary Source="pack://application:,,,/Han.Wpf.ViewportControl;component/Themes/Generic.xaml" />
</Application.Resources>
Usage:
<Grid width="1200" height="1200">
<Button />
</Grid>
The source code for the control and theme is on my gist and can be found on my github along with a demo application that loads an image into the viewport control.
I have a WPF Image inside a Border I currently am able to click and drag to pan the image. I want to prevent users from dragging the image off the screen. Perhaps a minimum of 100 pixels should be showing at any border. That is, if a user drags the image all the way to the left, most of the image will disappear, but 100 pixels will still be hanging out toward the left boundary of the border.
Here's my current code for the MouseMove and LeftClick event:
private void img_Box_MouseMove(object sender, MouseEventArgs e)
{
if (!img_Box.IsMouseCaptured) return;
var tt = (TranslateTransform)((TransformGroup)img_Box.RenderTransform).Children.First(tr => tr is TranslateTransform);
Vector v = start - e.GetPosition(img_Border);
tt.X = origin.X - v.X;
tt.Y = origin.Y - v.Y;
}
private void img_Box_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
img_Box.CaptureMouse();
var tt = (TranslateTransform)((TransformGroup)img_Box.RenderTransform).Children.First(tr => tr is TranslateTransform);
start = e.GetPosition(img_Border);
origin = new System.Windows.Point(tt.X, tt.Y);
}
I think I should be able to put a pretty simple condition on that to achieve my desired result, but I can't figure out what the condition should be.
In order to select multiple checkbox within Wrap panel using Shift Key.
Capturing the Mouse down event position on Shift Keydown, on second mouse down with shift keydown, getting the 2 positions of the selection, then Need to Select the checkbox control with in the selected area.
How do I find the controls within 2 positions (System.Window.Point) or System.Windows.rect. The following code is selecting all of the checkbox within the wrappanel(lesscolorpanel).
private System.Windows.Point startPoint;
System.Windows.Point checkpPoint;
private System.Windows.Point PointWhereMouseIs;
private void LessColourPanel_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
{
if (startPoint == checkpPoint)
{
//GET THE MOUSE POSITION
startPoint = Mouse.GetPosition(lessColourPanel);
PointWhereMouseIs = checkpPoint;
}
else if(PointWhereMouseIs==checkpPoint)
{
//CAPTURE END MOUSE POSITION
PointWhereMouseIs = Mouse.GetPosition(lessColourPanel);
//FIND CONTROLS WIHIN RECTANGLE
Rect selareaRect = new Rect(startPoint, PointWhereMouseIs);
foreach (System.Windows.Controls.CheckBox chkitemBox in FindVisualChildren<System.Windows.Controls.CheckBox>(lessColourPanel))
{
var rectBounds = VisualTreeHelper.GetDescendantBounds(chkitemBox);
Vector vector = VisualTreeHelper.GetOffset(chkitemBox);
rectBounds.Offset(vector);
if (rectBounds.IntersectsWith(selareaRect))
{
chkitemBox.IsChecked = true;
}
}
startPoint = checkpPoint;
}
}
}
Got it, and for viewers refer Ashley Davis post
The credit goes to Ashley Davis.
I have a wpf window that hosts a control.
The window's height and width are set to SizeToControl.
Now I need to position this Window relative to the position of its parent window.(basically the Top right position).
(So my windows Top = ParentWindow.Top, and Left = ParentWindow.Left + ParentWindow.ActualWidth - control.ActualWidth so that my window is positioned inside the parent window but to its right corner)
So i will need to set the Top and Left of the window. To do this I need the Actual Width of the control that is being hosted inside it....but I can only get this once I actually do,
Window.Show(control,parent)
Is there a way to get around this problem? How do I get the actual rendered width of the control before it is actually shown?
Thanks!
Have you tried this approach?
public partial class ShellWindow : Window
{
public ShellWindow(IShellPresentationModel model)
{
InitializeComponent();
this.Loaded += ShellWindow_Loaded;
}
void ShellWindow_Loaded(object sender, RoutedEventArgs e)
{
var innerWindow = new InnerWindow();
innerWindow.Owner = this;
innerWindow.Loaded += InnerWindow_Loaded;
innerWindow.Show();
}
void InnerWindow_Loaded(object sender, RoutedEventArgs e)
{
var w = (InnerWindow)sender;
w.Owner = this;
w.Top = this.Top;
w.Left = this.Left + this.ActualWidth - w.ActualWidth;
}
}
I need to move a multiple textboxes with a mouse cursor.
I decided that I do it that way.
If a textbox is clicked (and Control button is pressed) textbox is added to list of selected items. Then when button is still pressed and when mouse moves I do an operation of moving controls. However my code doesn't work well. Textboxes are moving but very very fast. Here is my code
List<TextBox> items;
private void txtBox_PreviewMouseDown(object sender, RoutedEventArgs e)
{
isClicked = true;
startPoint = Mouse.GetPosition( (sender as TextBox).Parent);
items = CurrentSelection;
}
private void txtBox_PreviewMouseMove(object sender, RoutedEventArgs e)
{
Point mousePos = Mouse.GetPosition(parentCanvas);
if (isClicked)
{
foreach (TextBox item in items)
{
double left = Canvas.GetLeft(item);
double top = Canvas.GetTop(item);
Canvas.SetLeft(item, left + (startPoint.X - mousePos.X));
Canvas.SetTop(item, top + (startPoint.Y - mousePos.Y));
}
}
}
Basically I iterate through all selected items and change their position on canvas. However I probably calculate a new position in a wrong way.
The problem is, that you always calculate the delta to the initial start point. You must actualize startPoint after every call to txtBox_PreviewMouseMove. Somethin like...
private void txtBox_PreviewMouseMove(object sender, RoutedEventArgs e) {
Point mousePos = Mouse.GetPosition(parentCanvas);
if (isClicked){
foreach (TextBox item in items) {
double left = Canvas.GetLeft(item);
double top = Canvas.GetTop(item);
Canvas.SetLeft(item, left + (startPoint.X - mousePos.X));
Canvas.SetTop(item, top + (startPoint.Y - mousePos.Y));
}
startPoint=mousePoint;
}
}
...should do the job. Another thing I have seen, is that the direction is probably inverted. This can be easily corrected. Change the calculcation to...
Canvas.SetLeft(item, left + (mousePos.X-startPoint.X ));
Canvas.SetTop(item, top + (mousePos.Y-startPoint.Y));
... and this problem should also be gone.