I am trying to write a WPF application where you can draw circles on a window by double clicking it. So far I have this code:
public class ShapeAdorner : Adorner
{
private readonly Ellipse _circle;
public ShapeAdorner(UIElement adornedElement, Point circleCenter)
: base(adornedElement)
{
_circle = new Ellipse
{
Width = 10,
Height = 10,
Stroke = Brushes.Black,
StrokeThickness = 1.5
};
_circle.Margin =
new Thickness(left: circleCenter.X, top: circleCenter.Y, right: 0, bottom: 0);
base.AddVisualChild(_circle);
}
protected override Size ArrangeOverride(Size finalSize)
{
_circle.Arrange(new Rect(finalSize));
return finalSize;
}
protected override Size MeasureOverride(Size constraint)
{
_circle.Measure(constraint);
return constraint;
}
protected override Visual GetVisualChild(int index)
{
return _circle;
}
protected override int VisualChildrenCount
{
get { return 1; }
}
}
Here's the client code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(myLabel);
adornerLayer.Add(new ShapeAdorner(adornedElement: myLabel, circleCenter: e.GetPosition(myLabel)));
}
}
The circles are supposed to be centered at the point where you double click the window; however, the circles drawn by the code above are centered below and to the right of "the double click point". How can this be fixed?
EDIT: myLabel has Height=350 and Width=525. Let's say that I double click the point (X,Y); then the circle gets plotted at ((350+X)/2,(525+Y)/2).
EDIT 2: Just for completeness, here's the .xaml file:
<Window x:Class="Adorners.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Adorners project" Height="350" Width="525" MouseDoubleClick="Window_MouseDoubleClick">
<Grid>
<Label Name="myLabel" Content="my label" Background="Red"></Label>
</Grid>
</Window>
Where you set the margin you have to subtract the radius from the top and left properties to offset the circle.
You'll need to offset by half the width/height of the circle. Hard-coded here to make it easy to follow:
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(myLabel);
var point = e.GetPosition(myLabel);
point.X -= 5;
point.Y -= 5;
adornerLayer.Add(new ShapeAdorner(myLabel, point));
You need to take the witdth and height into conserderation when you're setting the margin. the top should be the centerposition minus half of the height, and the same for the left:
new Thickness(
left: circleCenter.X + (_circle.Width/2), //farther to the right
top: circleCenter.Y - (_circle.Height/2), //higher up
right: 0, bottom: 0);
The previous answers are correct. However, the main problem was that I had omitted these two lines:
_circle.HorizontalAlignment = HorizontalAlignment.Left;
_circle.VerticalAlignment = VerticalAlignment.Top;
The default value Stretch caused a huge offset error.
Related
I'm trying to place an object on a random location depending on the window size.
LayoutRoot is the name of the grid it's placed in.
//Give Dot a random position
int left = random.Next(LayoutRoot.MinWidth, LayoutRoot.MaxWidth);
int top = random.Next(-900, 900);
Dot.Margin = new Thickness(left, top, 0, 0);
Error on LayoutRoot.MinWidth & MaxWidth: Cannot convert double to int
tried
//Give Dot a random position
double left = random.NextDouble(LayoutRoot.MinWidth, LayoutRoot.MaxWidth);
double top = random.Next(-900, 900);
Dot.Margin = new Thickness(left, top, 0, 0);
Error on NextDouble: Method NextDouble takes 2 arguments
Ok going to up this one up a bit. Modified your code. This code doesn't assume any predefined height or width, it gets that based on the current size of the LayoutRoot. It also offsets the Dot size so it doesn't fall off the screen.
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
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:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="LayoutRoot">
<Ellipse Width="2" Height="2" Fill="Black" x:Name="Dot"></Ellipse>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow
{
private static Random random = new Random();
public MainWindow()
{
InitializeComponent();
// Don't move dot until the window is loaded, not necessary but generally you don't want to hold up the window from displaying.
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
// The farthest left the dot can be
double minLeft = 0;
// The farthest right the dot can be without it going off the screen
double maxLeft = LayoutRoot.ActualWidth - Dot.Width;
// The farthest up the dot can be
double minTop = 0;
// The farthest down the dot can be without it going off the screen
double maxTop = LayoutRoot.ActualHeight - Dot.Height;
double left = RandomBetween(minLeft, maxLeft);
double top = RandomBetween(minTop, maxTop);
Dot.Margin = new Thickness(left, top, 0, 0);
}
private double RandomBetween(double min, double max)
{
return random.NextDouble() * (max - min) + min;
}
}
}
this might work.
double mWidth = LayoutRoot.MinWidth;
double mxWidth = LayoutRoot.MaxWidth;
double left = random.NextDouble(mWidth, mxWidth);
double top = random.Next(-900, 900);
Dot.Margin = new Thickness(left, top, 0, 0);
}
So after looking at the answer given by Kelly I had a similar issue where the object would not display after loading. My goal was to have the welcome screen of an application randomly move every 10 seconds. So following his design there was an issue where because the grid did not have an actual length due to the lack of column/row definitions we have to force the app to recalculate size at every attempt to randomize.
Here is the XAML I used:
<Grid x:Name="LayoutRoot">
<TextBlock x:Name="WelcomeTextBlock"
Height="200"
Style="{StaticResource WelcomeTextBlock}">
Welcome!
</TextBlock>
<Ellipse Width="2" Height="2" x:Name="Dot"/>
</Grid>
And more importantly the actual code-behind:
public partial class WelcomePage : Page
{
private static Random Rnd = new Random();
private static DispatcherTimer _timer;
public WelcomePage()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.Unloaded += OnClosing;
}
private void OnLoaded(object sender, RoutedEventArgs args)
{
this.LayoutRoot.Margin = GetNewMargin();
_timer = new DispatcherTimer {Interval = TimeSpan.FromSeconds(5)};
_timer.Tick += MoveWelcome;
_timer.Start();
}
private void MoveWelcome(object sender, EventArgs e)
{
this.LayoutRoot.Margin = GetNewMargin();
}
private Thickness GetNewMargin()
{
this.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
this.Arrange(new Rect(0, 0, this.DesiredSize.Width, this.DesiredSize.Height));
var maxLeft = this.LayoutRoot.ActualWidth - Dot.Width;
var maxTop = LayoutRoot.ActualHeight - Dot.Height;
var left = RandomBetween(0, maxLeft);
var top = RandomBetween(0, maxTop);
return new Thickness(left, top, 0 ,0);
}
private static void OnClosing(object sender, RoutedEventArgs args)
{
_timer.Stop();
}
private static double RandomBetween(double min, double max) => Rnd.NextDouble() * (max - min) + max;
Besides the timer functionality the major change here is that when the margin thickness is being calculated we force the window, or in my case page to update the measurements of the screen and arrange the items correctly.
I try to add a line on top of an image. So far it works when I add a line in a new project where I use only a grid and an image.
On the other hand the added line is not shown when I use the same code in another project where I also use a grid and an image with other elements. I suppose the line is added but hidden from the image itself or behind another control, grid or border. So my question is, how can I put my line as the topmost in front of the image?
grid2.Children.Add(myLine);
The outline looks like this:
Window -> Grid1-> Border-> Grid2-> Image (On this image I want to add a line)
The line element:
// Add a Line Element
static Line myLine = new Line
{
Stroke = Brushes.GreenYellow,
StrokeThickness = 2,
Visibility = Visibility.Visible
};
Here I read both points for the line:
private void image_zoom0_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (_firstPoint)
{
grid2.Children.Remove(myLine); // remove line first
System.Windows.Point position = Mouse.GetPosition(image_zoom0);
myLine.X1 = position.X;
myLine.Y1 = position.Y;
_firstPoint = false;
}
else
{
System.Windows.Point position = Mouse.GetPosition(image_zoom0);
myLine.X2 = position.X;
myLine.Y2 = position.Y;
_firstPoint = true;
grid2.Children.Add(myLine); // draw line
Canvas.SetZIndex(myLine,99);
}
}
}
I am not sure what kind of behavior you are expecting from this code. However, I created a sample wpf app and copied image_zoom0_MouseLeftButtonDown method as is.
Note-
I have added Stretch="UniformToFill" for the Image, so that it
occupy whole screen.
I have initialized _firstPoint to "false".
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" Height="350" Width="525" WindowState="Maximized">
<Grid x:Name="grid1">
<Border>
<Grid x:Name="grid2">
<Image x:Name="image_zoom0" Source="Background3.jpg" MouseLeftButtonDown="image_zoom0_MouseLeftButtonDown" Stretch="UniformToFill"/>
</Grid>
</Border>
</Grid>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
static Line myLine = new Line
{
Stroke = Brushes.GreenYellow,
StrokeThickness = 2,
Visibility = Visibility.Visible
};
bool _firstPoint;
private void image_zoom0_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (_firstPoint)
{
grid2.Children.Remove(myLine); // remove line first
System.Windows.Point position = Mouse.GetPosition(image_zoom0);
myLine.X1 = position.X;
myLine.Y1 = position.Y;
_firstPoint = false;
}
else
{
System.Windows.Point position = Mouse.GetPosition(image_zoom0);
myLine.X2 = position.X;
myLine.Y2 = position.Y;
_firstPoint = true;
grid2.Children.Add(myLine); // draw line
//Canvas.SetZIndex(myLine, 99);
}
}
}
}
Now according to the logic written inside the MouseLeftButtonDown, when you first click on the image a Line is drawn from TopLeft of the Window to the current mouseposition.
Second click will simply remove the line. Third click will again draw the line from your previous mouse position to current mouse position and fourth click will again remove it and so on.
I'm using an adorner layer to make a grid (like, boxes across the entire screen) across my grid (WPF grid).
I want this to be shown ONLY when a checkbox is marked. However, when I bind the "IsEnabled" property, nothing happens. Even if I set the IsEnabled to false, the grid overlay is still shown!
This is how I do from my mainWindow.xaml, note the IsEnabled is set to false, but it is still showing up:
<Grid>
<!--Adornerdecorator is made to make sure the adorner is in the background, and not the foreground-->
<AdornerDecorator>
<View:GridWithRulerxaml IsEnabled="False" />
</AdornerDecorator>
<ItemsControl ItemsSource="{Binding Classes}"/>
<ItemsControl ItemsSource="{Binding Edges}"/>
</Grid>
this is the adorner usercontrol:
namespace UMLDesigner.View
{
/// <summary>
/// Interaction logic for GridWithRulerxaml.xaml
/// </summary>
public partial class GridWithRulerxaml : UserControl
{
public GridWithRulerxaml()
{
InitializeComponent();
//Loaded event is necessary as Adorner is null until control is shown.
Loaded += GridWithRulerxaml_Loaded;
}
void GridWithRulerxaml_Loaded(object sender, RoutedEventArgs e)
{
var adornerLayer = AdornerLayer.GetAdornerLayer(this);
var rulerAdorner = new AlignmentAdorner(this);
adornerLayer.Add(rulerAdorner);
}
}
}
And finally the adorner itself:
namespace UMLDesigner.Utilities
{
public class AlignmentAdorner : Adorner
{
private FrameworkElement element;
public AlignmentAdorner(UIElement el)
: base(el)
{
element = el as FrameworkElement;
}
protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
{
base.OnRender(drawingContext);
double height = element.ActualHeight;
double width = element.ActualWidth;
double linesHorizontal = height / 50;
double linesVertical = width / 50;
var pen = new Pen(Brushes.LightGray, 1) { StartLineCap = PenLineCap.Triangle, EndLineCap = PenLineCap.Triangle };
int offset = 0;
for (int i = 0; i <= linesVertical; ++i)
{
offset = offset + 50;
drawingContext.DrawLine(pen, new Point(offset, 0), new Point(offset, height));
}
offset = 0;
for (int i = 0; i <= linesHorizontal; ++i)
{
offset = offset + 50;
drawingContext.DrawLine(pen, new Point(0, offset), new Point(width, offset));
}
}
}
}
I really hope you can help me out here guys.
The IsEnabled property only enables/disables interaction with the element, and has nothing to do with the Visibility.
What you should do is set the Visibility property of the GridWithRulerxaml to Collapsed or Hidden when you want to hide it, and set it to Visible when you want it shown.
Edit: Tested it, setting the visiblity to Hidden of the GridWithRulerxaml usercontrol inside the AdornerDecorator doesn't hide the adorner.
And thinking some more about it, if this isn't what you want, it might be possible to do this with the IsEnabled property, watching for changes in it and adding/removing the adorner depending on the value.
I'm working on a Adorner for a Line in a drawing program using WPF. The Line is drawn in code-behind and then adorned with my custom Adorner called LineAdorner. I've managed to use a Thumb for the start and end point of the Line. My problem is the arrangement of the Thumbs regarding the start and end points. I think the problem is in the method ArrangeOverride, where is supposed to arrange the Thumbs with the start and end points. I can't find the right amount to subtract or add in the Rect X and Y parameters. How can i find these values to always arrange the Thumbs with the points of the Line?
The code from my custom Adorner is this:
public class LineAdorner : Adorner
{
private Point start;
private Point end;
private Thumb startThumb;
private Thumb endThumb;
private Line selectedLine;
private VisualCollection visualChildren;
// Constructor
public LineAdorner(UIElement adornedElement) : base(adornedElement)
{
visualChildren = new VisualCollection(this);
startThumb = new Thumb { Cursor = Cursors.Hand, Width = 10, Height = 10, Background = Brushes.Green };
endThumb = new Thumb { Cursor = Cursors.Hand, Width = 10, Height = 10, Background = Brushes.BlueViolet };
startThumb.DragDelta += StartDragDelta;
endThumb.DragDelta += EndDragDelta;
visualChildren.Add(startThumb);
visualChildren.Add(endThumb);
selectedLine = AdornedElement as Line;
}
// Event for the Thumb Start Point
private void StartDragDelta(object sender, DragDeltaEventArgs e)
{
Point position = Mouse.GetPosition(this);
selectedLine.X1 = position.X;
selectedLine.Y1 = position.Y;
}
// Event for the Thumb End Point
private void EndDragDelta(object sender, DragDeltaEventArgs e)
{
Point position = Mouse.GetPosition(this);
selectedLine.X2 = position.X;
selectedLine.Y2 = position.Y;
}
protected override int VisualChildrenCount { get { return visualChildren.Count; } }
protected override Visual GetVisualChild(int index) { return visualChildren[index]; }
protected override void OnRender(DrawingContext drawingContext)
{
if (AdornedElement is Line)
{
selectedLine = AdornedElement as Line;
start = new Point(selectedLine.X1, selectedLine.Y1);
end = new Point(selectedLine.X2, selectedLine.Y2);
}
}
protected override Size ArrangeOverride(Size finalSize)
{
var startRect = new Rect(selectedLine.X1, selectedLine.Y1, ActualWidth, ActualHeight);
startThumb.Arrange(startRect);
var endRect = new Rect(selectedLine.X2, selectedLine.Y2, ActualWidth, ActualHeight);
endThumb.Arrange(endRect);
return finalSize;
}
}
Try this in your ArrangeOverride. You can get rid of the "start" and "end" variables, and you don't need to override OnRender since your Thumbs will render themselves if you tell them where they need to be.
protected override Size ArrangeOverride(Size finalSize)
{
selectedLine = AdornedElement as Line;
double left = Math.Min(selectedLine.X1, selectedLine.X2);
double top = Math.Min(selectedLine.Y1, selectedLine.Y2);
var startRect = new Rect(selectedLine.X1 - (startThumb.Width / 2), selectedLine.Y1 - (startThumb.Width / 2), startThumb.Width, startThumb.Height);
startThumb.Arrange(startRect);
var endRect = new Rect(selectedLine.X2 - (endThumb.Width / 2), selectedLine.Y2 - (endThumb.Height / 2), endThumb.Width, endThumb.Height);
endThumb.Arrange(endRect);
return finalSize;
}
You are setting an explicit size on the Thumbs, so that had to be maintained in the Arrange. Also, you need to subtract half the width and height of the Thumbs to center on the end points.
Due to the nature of the Canvas and Shapes, you need to subtract the "real" left and top values of the line, since unlike the Line, the Adorners are not going to draw themselves from the top-left on the Canvas. This shouldn't be required outside of using Canvasses.
I'm attempting to create an application that looks much like the Windows 8 Metro UI with the tiles.. right now if you click on a tile I have an animation that increases the width and reveals more info on that tile.
I have the tiles laid out in a WrapPanel which is nice because if I resize the tile the other tiles next to it move and keep it's margin perfectly. However I've been researching this for awhile, I would like to if possible limit the number of items in the wrap panel to two wide (Right now it's three wide) as if you select one of the tiles it resizes itself and pushes a tile next to it (or if it's the end tile it will push itself) to the next row, while my animations are smooth it doesn't look the best presentation-wise..
Could someone point me towards how I might specify my wrappanel to only have a width of two items across?
Any help is very much appreciated.
Try making your own wrap panel deriving from standard wrap panel as described in this post in detail. Post addresses the same issue which you are trying to solve.
public class MyWrapPanel : WrapPanel
{
public int MaxRows
{
get { return (int)GetValue(MaxRowsProperty); }
set { SetValue(MaxRowsProperty, value); }
}
public static readonly DependencyProperty MaxRowsProperty =
DependencyProperty.Register("MaxRows", typeof(int), typeof(MyWrapPanel), new UIPropertyMetadata(4));
protected override Size ArrangeOverride(Size finalSize)
{
Point currentPosition = new Point();
double ItemMaxHeight = 0.0;
int RowIndex = 0;
foreach (UIElement child in Children)
{
ItemMaxHeight = ItemMaxHeight > child.DesiredSize.Height ? ItemMaxHeight : child.DesiredSize.Height;
if (currentPosition.X + child.DesiredSize.Width > this.DesiredSize.Width)
{
currentPosition = new Point(0, currentPosition.Y + ItemMaxHeight);
ItemMaxHeight = 0.0;
RowIndex++;
}
if (RowIndex < MaxRows)
{
child.Visibility = System.Windows.Visibility.Visible;
Rect childRect = new Rect(currentPosition, child.DesiredSize);
child.Arrange(childRect);
}
else
{
Rect childRect = new Rect(currentPosition, new Size(0,0));
child.Arrange(childRect);
}
currentPosition.Offset(child.DesiredSize.Width, 0);
}
return finalSize;
}
protected override Size MeasureOverride(Size availableSize)
{
return base.MeasureOverride(availableSize);
}
}