I'm working on a tiny color picker in WPF. I have this...
<Border
Background="Transparent"
BorderBrush="Black"
BorderThickness="2"
SnapsToDevicePixels="True">
<Canvas x:Name="ColorPlaneCanvas"
Width="400"
Height="400"
Background="Transparent"
MouseLeftButtonDown="{s:Action PlanePositionChanged}"
MouseMove="{s:Action PlanePositionChanged}"
SnapsToDevicePixels="True">
<Ellipse x:Name="ColorPlane"
Canvas.Left="{Binding PlaneX}"
Canvas.Top="{Binding PlaneY}"
Width="10"
Height="10"
Fill="Transparent"
SnapsToDevicePixels="True"
Stroke="White"
StrokeThickness="2" />
</Canvas>
</Border>
I use a canvas (on top of another canvas), which contains a little "plane". When I drag the mouse on the top canvas, the plane's coordinates updates, as MouseLeftButtonDown event gets called.
But if I move the mouse to the Canvas'side (any side, which is not part of the canvas, but the containing elements), I can still get the MouseLeftButtonDown event.
What can I do to get the mouse event only within the Canvas?
Here is a working version, if you want to see the artifact.
i did a similar program that tells u the cords of the mouse in the canvas and if u click it paints a line, ill send u the wpf and the code, see if it helps, i use a point and it works.
WPF:
<Canvas x:Name="cnvPaper" Background="Aquamarine" Margin="30" MouseEnter="cnvPaper_MouseEnter" MouseLeave="cnvPaper_MouseLeave" MouseDown="cnvPaper_MouseDown" MouseMove="cnvPaper_MouseMove"/>
C#
Point previousPoint;
private void cnvPaper_MouseEnter(object sender, MouseEventArgs e)
{
previousPoint = e.GetPosition(cnvPaper);
}
private void cnvPaper_MouseMove(object sender, MouseEventArgs e)
{
String cordsText = "X: " + e.GetPosition(cnvPaper).X + " - Y: " + e.GetPosition(cnvPaper).Y;
if (e.LeftButton == MouseButtonState.Pressed)
{
DrawLine(e.GetPosition(cnvPaper));
}
else if(e.RightButton == MouseButtonState.Pressed)
{
DrawCircle(e.GetPosition(cnvPaper));
}
previousPoint = e.GetPosition(cnvPaper);
}
Related
the case is this one:
I have an image representing a schema, let's say a cross like the following
I need to include the image in a WPF UserControl and let the user click on each of the branches (red, green or blue...) and, according to the branch selected, do something different.
What would be the best way to solve this?
I tried with canvas but I don't find a way to trace correctly the background image with shapes (also because the real image is not so simple as the sample cross here)
thanks for any suggestion
It depends on the comlexity of shapes but it is not difficult to find the shape where mouse up event is fired by VisualTreeHelper.HitTest method.
Let's say there is a Canvas which has two Rectangles inside.
<Canvas Background="Transparent"
PreviewMouseUp="Canvas_PreviewMouseUp">
<Rectangle x:Name="Rect1" Canvas.Left="20" Canvas.Top="20"
Width="20" Height="20" Fill="Red"/>
<Rectangle x:Name="Rect2" Canvas.Left="80" Canvas.Top="80"
Width="20" Height="20" Fill="Green"/>
</Canvas>
Catching its PreviewMouseUp event, you can tell the Rectangle where that event is fired.
private void Canvas_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
var position = e.GetPosition((IInputElement)sender);
var result = VisualTreeHelper.HitTest((Visual)sender, position);
if (result.VisualHit is Rectangle rect)
{
Debug.WriteLine($"Hit {rect.Name}");
}
}
I want to achieve a very well known behavior seen in the browser when you have an image to display that is larger then the monitor:
Originally, the image is displayed fitting inside the window area, and the mouse cursor is a magnifying glass with a "+" icon;
If you click, two things happen:
a. The image is displayed with its native pixel size;
b. Scroll bars appear;
I want this effect with a larger-than-screen UniformGrid. For that, I can use ViewBox. I have already got what I want putting the control inside a ViewBox with Stretch.Uniform property, and upon MouseLeftButtonDown event it toggles between Stretch.None and Stretch.Uniform, just like the large image in browser analogy, only without scroll bars.
Now if I add the ScrollViewer (ViewBox -> ScrollViewer -> UniformGrid), the effect doesn't work anymore, because the ScrollViewer always displays the (larger than window) MyUserControl with its native resolution, that is, clipped and with scroll bars activated, while I would like to alternate between this and a "fitting in ViewBox" version.
Here is how I get the resizing, but the ScrollViewer never displays:
<Viewbox x:Name="vbox" Stretch="None">
<ScrollViewer x:Name="scroll" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" >
<UniformGrid x:Name="ugrid" Columns="2" MouseLeftButtonDown="UniformGrid_MouseLeftButtonDown">
<local:AtlasMasculinoAnterior/>
<local:AtlasMasculinoPosterior/>
</UniformGrid>
</ScrollViewer>
</Viewbox>
And if change the order, then the Scroll bars always display and the zoom doesn't toggle upon mouse click (although the event fires):
<ScrollViewer x:Name="scroll" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" >
<Viewbox x:Name="vbox" Stretch="None">
<UniformGrid x:Name="ugrid" Columns="2" MouseLeftButtonDown="UniformGrid_MouseLeftButtonDown">
<local:AtlasMasculinoAnterior/>
<local:AtlasMasculinoPosterior/>
</UniformGrid>
</Viewbox>
</ScrollViewer>
And here the code behind event:
private void UniformGrid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (vbox.Stretch == Stretch.None)
{
vbox.Stretch = Stretch.Uniform;
}
else
vbox.Stretch = Stretch.None;
}
So what am I doing wrong, or what should I do so that the intended behavior works?
The way I see it, I would like to alternate between having the control in a ViewBox (Stretch.Uniform) and having the control inside a ScrollViewer, but I wonder how to have the same effect with both elements being part of the layout tree (one inside another), or even if I should, move the UniformGrid in and out of containers I would manipulate programmatically in code behind.
Got it to work in sort of a hackish way, by having a Grid with both a ViewBox and a ScrollViewer, and putting the UniformGrid inside one of them in XAML. Then, in code-behind, I programmatically detach the UniformGrid from its present container, and attach it to the other (using a boolean flag to control where it is, but that is debatable):
<Grid x:Name="grid">
<ScrollViewer x:Name="scroll" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"/>
<Viewbox x:Name="viewbox" Stretch="Uniform">
<UniformGrid x:Name="ugrid" Columns="2" MouseLeftButtonDown="UniformGrid_MouseLeftButtonDown">
<local:AtlasMasculinoAnterior/>
<local:AtlasMasculinoPosterior/>
</UniformGrid>
</Viewbox>
</Grid>
and
bool atlasfullscreen = false;
private void UniformGrid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
UniformGrid ug = sender as UniformGrid;
if (atlasfullscreen)
{
scroll.Content = null;
viewbox.Child = ug;
atlasfullscreen = false;
}
else
{
viewbox.Child = null;
scroll.Content = ug;
atlasfullscreen = true;
}
}
I had a similar use case where I had an item that I needed to alternate between Stretch.None and Stretch.Uniform, and when Stretch.None, I needed the scrollbars to be visible.
What I finally figured out was that when I set Stretch.None, I needed to set the ScrollViewer's Width & Height to the ViewBox's parent ActualWidth / Height, and when Stretch.Uniform, I needed to clear the ScollViewer's width and height.
So using your original XAML, plus the new Grid, here's the new XAML:
<Grid x:Name="grid">
<Viewbox x:Name="vbox"
Stretch="Uniform">
<ScrollViewer x:Name="scroll"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<UniformGrid x:Name="ugrid"
Columns="2"
MouseLeftButtonDown="UniformGrid_MouseLeftButtonDown">
<local:AtlasMasculinoAnterior />
<local:AtlasMasculinoPosterior />
</UniformGrid>
</ScrollViewer>
</Viewbox>
</Grid>
New code behind:
private void UniformGrid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (vbox.Stretch == Stretch.None)
{
vbox.Stretch = Stretch.Uniform;
scroll.Width = double.NaN;
scroll.Height = double.NaN;
}
else
{
vbox.Stretch = Stretch.None;
scroll.Width = grid.ActualWidth;
scroll.Height = grid.ActualHeight;
}
}
You might need to tweak the above example for how the Viewbox now being in a grid - but for my use case with similar XAML / code I got mine working without having to constantly move the child from the Viewbox to another control and back again.
So in summary: when Viewbox.Stretch = Uniform, set scrollviewer's width / height to double.NaN, and when Viewbox.Stretch = None, set scrollviewer's width / height to Viewbox.Parent.ActualWidth / Height.
I am trying to create a canvas with scroll bars.
Can anyone help me give some ideas on how to do this?
I have already tried using grid of 1 row and 1 column but due to certain constraints I want to use canvas.
Thanks in advance!
You could put the canvas inside of a scrollviewer. I tried this quick test and it allowed me to scroll through the contents of the canvas.
<ScrollViewer Height="100" Width="200">
<Canvas Height="400" Width="400">
//Content here
</Canvas>
</ScrollViewer>
edit: Here is an example where the scroll-bars show up only when needed, and it changes dynamically as the canvas size changes.
<Button Content="Change Canvas Size" Click="ChangeCanvasSize_Click"/>
<ScrollViewer Height="100" Width="200" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<Canvas x:Name="TestCanvas">
<TextBlock Text="Test Test"/>
</Canvas>
</ScrollViewer>
Changing canvas size with button click:
private void ChangeCanvasSize_Click(object sender, RoutedEventArgs e)
{
TestCanvas.Width = 600;
TestCanvas.Height = 600;
}
In this example, I start out with no scroll-bars and when I click the button to expand the canvas, scroll-bars appear.
Ok after working with it for sometime I figured out a way. Create a XAML like this
<ScrollViewer>
<Grid x:Name="drawingGrid" SizeChanged="drawingGrid_SizeChanged">
<Canvas Name="drawingCanvas"> /<Canvas>
</Grid>
</ScrollViewer>
On windowLoad function set the canvas height/width equal to grid height/width. Update the canvas ht/wd:
when grid size changes, due to mininmize/maximize.
dragging an element beyond the boundaries of canvas or creating a new element too close the edge of canvas
double dHeight = 220;
if (drawingCanvas.Height < CurrentPosition.Y + dHeight)
{
// increase canvas height
drawingCanvas.Height += (2 * dHeight);
}
Hope this is of some help. Please share if anyone has any better idea or suggestions to improve this.
By combining Mario-sannum's answer and your question then I've made a solution that should work fine in most cases..
<ScrollViewer>
<Grid x:Name="drawingGrid" SizeChanged="drawingGrid_SizeChanged">
<Canvas Name="c">
<TextBlock x:Name="draw_Text" Text="Test Test"/>
</<Canvas>
</Grid>
</ScrollViewer>
void drawingGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
try { c.Height = draw_Text.ActualHeight; } catch { }
try { c.Width = draw_Text.ActualWidth; } catch { }
}
That should resize the Canvas so the scrollviewer can scroll...
I need to rotate the UI 180 degrees of my surface application in runtime when a user presses a button. How do I do this?
Just apply a RotateTransform on your topmost panel (I think you can even do it on the actual surface window if you want) with an angle of 180 degrees.
<s:SurfaceWindow x:Class="SurfaceApplication1.SurfaceWindow1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="http://schemas.microsoft.com/surface/2008"
Title="SurfaceApplication1">
<Grid>
<Grid.LayoutTransform>
<RotateTransform x:Name="mainOrientation"/>
</Grid.LayoutTransform>
<s:SurfaceButton Click="btn_Click" Content="Click to rotate" />
... other content here ...
</Grid>
</s:SurfaceWindow>
And in code behind:
private void btn_Click (object sender, RoutedEventArgs e)
{
if (mainOrientation.Angle == 0)
mainOrientation.Angle = 180;
else
mainOrientation.Angle = 0;
}
As a related topic, you can also listen to the surface's OrientationChanged event to know when a user has changed side of your app. A good practice is to flip the app to the correct side when this happens.
We're trying to implement drag and drop in Silverlight (3). We want users to be able to drag elements from a treeview onto another part of a UI. The parent element is a Grid, and we've been trying to use a TranslateTransform along with the MouseLeftButtonDown, MouseMove (etc) events, as recommended by various online examples. For example:
http://www.85turns.com/2008/08/13/drag-and-drop-silverlight-example/
We're doing this in IronPython, but that should be more or less irrelevant. The drag start is correctly initiated, but the item we are dragging appears in the 'wrong' location (offset a few hundred pixels to the right and down from the cursor) and I can't for the life of me work out why.
Basic xaml:
<Grid x:Name="layout_root">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="120"/>
</Grid.RowDefinitions>
<Border x:Name="drag" Background="LightGray" Width="40" Height="15"
Visibility="Collapsed" Canvas.ZIndex="10">
<Border.RenderTransform>
<TranslateTransform x:Name="transform" X="0" Y="0" />
</Border.RenderTransform>
<TextBlock x:Name="dragText" TextAlignment="Center"
Foreground="Gray" Text="foo" />
</Border>
...
</Grid>
The startDrag method is triggered by the MouseLeftButtonDown event (on a TextBlock in a TreeViewItem.Header). onDrag is triggered by MouseMove. In the following code self.root is Application.Current.RootVisual (top level UI element from app.xaml):
def startDrag(self, sender, event):
self.root.drag.Visibility = Visibility.Visible
self.root.dragText.Text = sender.Text
position = event.GetPosition(self.root.drag.Parent)
self.root.drag.transform.X = position.X
self.root.drag.transform.Y = position.Y
self.root.CaptureMouse()
self._captured = True
def onDrag(self, sender, event):
if self._captured:
position = event.GetPosition(self.root.drag.Parent)
self.root.drag.transform.X = position.X
self.root.drag.transform.Y = position.Y
The dragged item follows the mouse move, but is offset considerably. Any idea what I am doing wrong and how to correct it?
So it turns out that I should have been setting the Margin instead of using TranslateTransform:
def startDrag(self, sender, event):
self.root.drag.Visibility = Visibility.Visible
self.root.dragText.Text = sender.Text
self.root.CaptureMouse()
self._captured = True
self.root.MouseLeftButtonUp += self.stopDrag
self.root.MouseLeave += self.stopDrag
self.onDrag(sender, event)
def onDrag(self, sender, event):
if self._captured:
position = event.GetPosition(self.root.layout_root)
self.root.drag.Margin = Thickness(position.X, position.Y, 0, 0)
self.root.drag.UpdateLayout()