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
Related
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 writing a WPF application. All icons I use are vector and stored in standalone ResourceDictionary as series of Viewboxes:
<ResourceDictionary>
<ViewBox x:Shared="False" x:Key="IconKey" Stretch="Uniform">
<Canvas Width="16" Height="16">
<Path ... />
</Canvas>
</ViewBox>
<!-- ... -->
</ResourceDictionary>
I'm implementing now a user control, which, due to very specific requirements, is drawn from scratch by me in OnRender method.
I need to render these icons on my control. One solution is to rasterize them and then draw bitmaps, but the problem is, that view in my control can be freely zoomed, and such icons would look ugly when zoomed in (as opposed to vector ones). Is there a way to render a ViewBox using the DrawingContext?
I don't know how you can render ViewBox but you can render Geometry and that will not be ugly when zoomed.
I have created icon in a XAML file. Build Action: Page.
File:Icon.xaml
<?xml version="1.0" encoding="UTF-8"?>
<Viewbox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Stretch="Uniform">
<Canvas Width="25" Height="25">
<Path Fill="#FF000000" StrokeThickness="0.4">
<Path.Data>
<PathGeometry Figures="M 4.9586532 4.4020592 12.198751 19.198359 V 0.91721594 H 20.200561 h 2.894515 Z" FillRule="Nonzero"/>
</Path.Data>
</Path>
</Canvas>
</Viewbox>
To draw this with DrawingContext create method like this
void DrawIcon(DrawingContext context, double left, double top, double scale = 1)
{
var transform = new MatrixTransform(scale, 0, 0, scale, left, top);
var viewBox = (Viewbox) Application.LoadComponent(new Uri("/MyProjectNameSpace;component/Icon.xaml", UriKind.Relative));
var canvas = viewBox.Child as Canvas;
if (canvas?.Children == null) return;
foreach (UIElement child in canvas.Children)
if (child is Path path)
{
path.Data.Transform = transform;
context.DrawGeometry(Brushes.Black, new Pen(Brushes.Black, 1), path.Data);
}
}
my problem is the following: In my program I let the user place shapes (class DrawingShape) on a Canvas. The Drawing Shape encapsulates a stacked path and label:
<UserControl x:Class="HandballTrainerFluent.Graphics.DrawingShape"
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"
d:DesignHeight="60"
d:DesignWidth="60"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid x:Name="container" Width="Auto" Height="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="38"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Canvas x:Name="geometryCanvas" HorizontalAlignment="Center" Grid.Row="0" Width="38" Height="38">
<Path x:Name="Path"
Width="35.8774"
Height="31.2047"
Canvas.Left="1.0613"
Canvas.Top="3.29528"
Stretch="Fill"
StrokeLineJoin="Round"
Stroke="{Binding OutlineBrush,Mode=OneWay}"
StrokeThickness="{Binding OutlineWidth,Mode=OneWay}"
StrokeDashArray="{Binding OutlinePattern,Mode=OneWay}"
Fill="{Binding FillBrush,Mode=OneWay}"
Data="F1 M 19,3.79528L 1.5613,34L 36.4387,34L 19,3.79528 Z "/>
</Canvas>
<TextBlock x:Name="TextBox" HorizontalAlignment="Center" Grid.Row="1" Text="{Binding LabelText,Mode=OneWay}"></TextBlock>
</Grid>
</UserControl>
So some visual setting and the label text are bound to Properties of the code-behind file.
After deserializing a Canvas with these Drawing shapes, I need to restore the binding between the XAML and the code-behind file. I've tried this, but it does not seem to work:
private void RepairBindingsAfterLoading()
{
foreach (UIElement element in this.drawingCanvas.Children)
{
if (element.GetType() == typeof(DrawingShape))
{
DrawingShape shape = (DrawingShape)element;
BindingOperations.ClearAllBindings(shape.Path);
BindingOperations.ClearAllBindings(shape.TextBox);
BindingOperations.ClearAllBindings(shape);
shape.BeginInit();
Binding dataContextBinding = new Binding();
dataContextBinding.RelativeSource = RelativeSource.Self;
shape.SetBinding(DrawingShape.DataContextProperty, dataContextBinding);
Binding fillBinding = new Binding("FillBrush");
shape.Path.SetBinding(Path.FillProperty, fillBinding);
Binding outlineBinding = new Binding("OutlineBrush");
shape.Path.SetBinding(Path.StrokeProperty, outlineBinding);
Binding widthBinding = new Binding("OutlineWidth");
shape.Path.SetBinding(Path.StrokeThicknessProperty, widthBinding);
Binding patternBinding = new Binding("OutlinePattern");
shape.Path.SetBinding(Path.StrokeDashArrayProperty, patternBinding);
Binding labelTextBinding = new Binding("LabelText");
shape.TextBox.SetBinding(TextBlock.TextProperty, labelTextBinding);
shape.EndInit();
shape.UpdateLayout();
}
}
}
No matter what I do to the code-behind Properties (e.g. change FillBrush), the visuals of the displayed DrawingShape won't update. Am I missing an important step here?
I've added shape.BeginUpdate() and shape.EndUpdate() after seeing this question: Bindings not applied to dynamically-loaded xaml
Thanks a lot for any insights
Edit 2012-09-25
Looking at another piece of code which does not depend on any bindings makes me wonder, if I can actually reference any elements from the Xaml-Definition via their x:Name after de-serialization. The following callback does not do anything on a shape:
private void rotateClockwiseMenuItem_Click(object sender, RoutedEventArgs e)
{
if(this.drawingCanvas.SelectedItem.GetType() == typeof(DrawingShape))
{
DrawingShape shape = (DrawingShape)this.drawingCanvas.SelectedItem;
TransformGroup transformStack = new TransformGroup();
transformStack.Children.Add(shape.geometryCanvas.LayoutTransform);
transformStack.Children.Add(new RotateTransform(90));
shape.geometryCanvas.LayoutTransform = transformStack;
}
}
Debugging tells me that the contents of shape seem just right. When I execute the command once, shape.geometryCanvas.LayoutTransformis the identity matrix. When executing it a second time, shape.geometryCanvas.LayoutTransform is a TransformGroup of two elements.
It somehow looks like the reference for geometryCanvas (declared in the Xaml) is no the one used on screen.
Got it!
I didn't know that you can't successfully reference x:Name'd XAML elements from outside the code-behind file after de-serialization (that at least seems to be the problem at hand).
A solution is to use FindName() on the UserControl, e.g.:
TextBlock textBox = shape.FindName("TextBox") as TextBlock;
The complete and correct RepairBindingsAfterLoading() looks like this:
private void RepairBindingsAfterLoading()
{
foreach (UIElement element in this.drawingCanvas.Children)
{
if (element.GetType() == typeof(DrawingShape))
{
DrawingShape shape = (DrawingShape)element;
shape.DataContext = shape;
Path path = shape.FindName("Path") as Path;
Binding fillBinding = new Binding("FillBrush");
path.SetBinding(Path.FillProperty, fillBinding);
Binding outlineBinding = new Binding("OutlineBrush");
path.SetBinding(Path.StrokeProperty, outlineBinding);
Binding widthBinding = new Binding("OutlineWidth");
path.SetBinding(Path.StrokeThicknessProperty, widthBinding);
Binding patternBinding = new Binding("OutlinePattern");
path.SetBinding(Path.StrokeDashArrayProperty, patternBinding);
TextBlock textBox = shape.FindName("TextBox") as TextBlock;
Binding labelTextBinding = new Binding("LabelText");
textBox.SetBinding(TextBlock.TextProperty, labelTextBinding);
}
}
}
Just for the record, my clumsy
BindingOperations.ClearAllBindings(shape.Path);
BindingOperations.ClearAllBindings(shape.TextBox);
BindingOperations.ClearAllBindings(shape);
works just like the much more simple and elegant solution suggested by dbaseman with:
shape.DataContext = this;
Hope this helps someone else to avoid my mistake :-)
I am using Silverlight 4 to access the web cam. Everything works ok when I start the web cam on a button click event, I get the prompt for permission. I would like the web cam to start when User Control loads, but for some reason when I run the same code on the Loaded event, I don't get a prompt when executing the following code:'
CaptureDeviceConfiguration.RequestDeviceAccess()
Does anyone have a work around for this?
I found a workaround to the problem. I am automatically clicking the button that starts the webcam streaming on the Loaded event of the control.
ButtonAutomationPeer peer = new ButtonAutomationPeer(btnStartWebcam);
IInvokeProvider invokeProv =
peer.GetPattern(PatternInterface.Invoke)
as IInvokeProvider;
invokeProv.Invoke();
This does the job for me because I don't mind having a button on the UI. But I guess you can create a dummy one and hide it if it's necessary.
The security around accessing local devices is very tight. Starting the capture must be preceded by a user action.
Instead of starting the capture from the loaded event, you'll have to move it to a Click event.
Code behind:
public void StartCam()
{
VideoCaptureDevice dev = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
if(CaptureDeviceConfiguration.RequestDeviceAccess() &&
CaptureDeviceConfiguration.AllowedDeviceAccess)
{
CaptureSource capture = new CaptureSource();
capture.VideoCaptureDevice = dev;
VideoBrush videoBrush = new VideoBrush();
videoBrush.SetSource(capture);
capture.Start();
WebCamRectangle.Fill = videoBrush;
}
}
private void button1_Click(object sender, RoutedEventArgs e)
{
StartCam();
}
Xaml:
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="49*" />
<RowDefinition Height="251*" />
</Grid.RowDefinitions>
<Rectangle Name="WebCamRectangle"
Stroke="Black" StrokeThickness="1" Grid.Row="1" />
<Button Content="Start" Height="25" HorizontalAlignment="Left"
Margin="12,12,0,0" Name="button1" VerticalAlignment="Top"
Width="135" Click="button1_Click" />
</Grid>
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()