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()
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'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);
}
NEW INFORMATION!!!
Meanwhile i have found a solution but there is a new problem.
The solution is to set the margin of the canvas in code-behind to a new object of type Thickness with top and left 1 or 2.
But the canvas is lying on a tabcontrol.
When i switch between tabs or make a mousedown on the canvas the margin is lost.
I'm working with VS2015 on a WPF-application and have a very curious problem.
I got in one of my WPF windows a canvas as parent for some child elements.
One of these elements is a rectangle which shall show the user the size of a DIN-A4 page.
It is added in code-behind to the children collection of the canvas.
Normally i would place that rectangle at position (0,0).
But because of some problems i have to trick and set the position to (-1,-1) like that:
public static System.Windows.Shapes.Rectangle GetRectangle(double top, double left, double width, double height)
{
var rectangle = new System.Windows.Shapes.Rectangle();
System.Windows.Controls.Canvas.SetLeft(rectangle, -1);
System.Windows.Controls.Canvas.SetTop(rectangle, -1);
rectangle.Width = Math.Round(GetSizeInPoint(width)) + 2;
rectangle.Height = Math.Round(GetSizeInPoint(height));
rectangle.StrokeThickness = 1;
rectangle.Stroke = System.Windows.Media.Brushes.Red;
return rectangle;
}
But the result of it is that just a small part of the top and left border of the rectangle can be seen.
Do i have a chance to "move" the canvas so that the rectangle is displayed completely?
Hereby another important problematic point is that the Grid named "grdProtocolDesigner" can be serialized to XML and saved in the database.
So a complete restructuring would be a big problem.
Here the relevant part of my XAML including the canvas:
<ContentPresenter x:Name="protocolContainer"
Grid.Row="1"
Grid.Column="0">
<ContentPresenter.Content>
<Grid x:Name="grdProtocolDesigner"
Grid.Row="1"
Grid.Column="0">
<ScrollViewer>
<!--Important! The background of this panel has to be "Transparent" so that drag'n'drop-events can work.
Otherwise the events are not fired.-->
<Canvas x:Name="protocolDesignerPanel"
AllowDrop="True"
Visibility="{Binding DesignerPanelsVisibility}"
Width="2200" Height="4000"
MouseEnter="designerpanel_MouseEnter"
MouseLeave="designerpanel_MouseLeave"
MouseDown="designerpanel_MouseDown"
MouseMove="designerpanel_MouseMove"
MouseUp="designerPanel_MouseUp">
<Canvas.RenderTransform>
<ScaleTransform x:Name="scaleProtocolLayout"/>
</Canvas.RenderTransform>
<Canvas.Background>
<ImageBrush ImageSource="../../pictures/CanvasBackground.png"
TileMode="Tile"
Stretch="None"
Viewport="0, 0, 10, 10"
ViewportUnits="Absolute" />
</Canvas.Background>
</Canvas>
</ScrollViewer>
</Grid>
</ContentPresenter.Content>
</ContentPresenter>
I'm currently creating a little soft where you can create your own Comic. I'm currently trying to add the speech bubbles. What i did is that i have put a Textbox inside a border with rounded corners.This is my result.
Now i would like to add an arrow that points towards the character speaking(Example of what i would like to get). The position of the arrow should be chosen by the user. It would like rotate around the border. I don't know if it's possible to do something like that. If it's not i'd like that the user could choose the direction of the arrow before adding the speech bubble(between the eight basic directions).
Here is the code i use to create my bubbles :
Border bdrBubble = new Border();
bdrBubble.BorderThickness = new Thickness(2);
bdrBubble.BorderBrush = Brushes.Black;
System.Windows.Controls.TextBox txtBubble = new System.Windows.Controls.TextBox();
txtBubble.Background = Brushes.White;
txtBubble.TextWrapping = TextWrapping.Wrap;
txtBubble.AcceptsReturn = true;
txtBubble.Background = Brushes.Transparent;
txtBubble.BorderThickness = new Thickness(0);
txtBubble.Text = tbxBubble.Text;
bdrBubble.CornerRadius = new CornerRadius(100);
txtBubble.ClipToBounds = true;
bdrBubble.Background = Brushes.White;
bdrBubble.Padding = new Thickness(10);
txtBubble.TextAlignment = TextAlignment.Center;
bdrBubble.Child = txtBubble;
Hope someone could point me towards the best solution !
There is no such existing functionality, but you can visit the following links and modify the following template as per your needs to change the direction of the arrow based on the co-ordinates.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<Rectangle Fill="#FF686868" Stroke="#FF000000" RadiusX="10" RadiusY="10"/>
<Path Fill="#FF686868" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left" Margin="30,-5.597,0,-0.003" Width="25" Grid.Row="1" Data="M22.166642,154.45381 L29.999666,187.66699 40.791059,154.54395"/>
<Rectangle Fill="#FF686868" RadiusX="10" RadiusY="10" Margin="1"/>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="25" Text="Hello World" TextWrapping="Wrap"/>
</Grid>
Links:
How to style WPF tooltip like a speech bubble?
How to implement Balloon message in a WPF application
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.