F# and Winforms - winforms

i have a program like this
let form = new Form()
let drawArea = new Panel(Location = new Point(200,0), Height = 600, Width = 800)
let rectBrush = new SolidBrush(Color.Blue)
form.Controls.Add(drawArea)
drawArea.MouseClick.Add(fun args ->
drawArea.Paint.Add(fun e ->
e.Graphics.FillRectangle(rectBrush, args.X, args.Y, 50, 50)))
Application.Run(form)
so when i click a blue rectangle appears. However, where are these rectangles stored? is there any way to retrive a list of all the rectangles being part of the "drawArea"?
Otherwise, is there a way to add rectangles as child controllers to a panel or simular winform object?
Thank you

I don't know of a way to retrieve these rectangles that way.
The best you can do is make a Rectangle class, and make it inherit from Control or UserControl. Then override it's protected OnPaint(...) method. For example:
public class FilledRectangle : UserControl
{
private readonly float x, y, w, h;
// also the brush here
public FilledRectangle(float x, float y, float w, float h)
{
this.x = x;
// ...
}
protected override void OnPaint(PaintEventArgs e) // not sure about the event args type name
{
e.Graphics.FillRectangle(this.myBrush, this.x, this.y, this.w, this.h);
}
}
Now you can simple add an object of this type to the Controls collection like so:
Controls.Add(new FilledRectangle(...));
With this approach you can easily create a collection of the rectangle objects and make them behave like you want them to.
Note that the examples I wrote above are in C#, but it shouldn't be too difficult to port it to F#. Only reason I'm not doing it is because I'm not very familiar with its syntax. Your question is not F#-specific, but .NET-specific.
Also note that as it has been commented by Hans, the Paint event won't fire until you call Invalidate().

Related

GMAP.NET adding labels underneath markers

I have just started using gmap.net and I was looking for the functionality of adding labels under the markers. I see there's tooltips but I would like to have a constant label under my marker with a one word description.
I searched for docs or other answers but I cannot find anything which leads me to believe that it is not implemented. If someone can verify this I would appreciate it.
You need to create your own custom marker.
Based on the source of GMapMarker and the derived GMarkerGoogle I came up with this simplified example:
public class GmapMarkerWithLabel : GMapMarker, ISerializable
{
private Font font;
private GMarkerGoogle innerMarker;
public string Caption;
public GmapMarkerWithLabel(PointLatLng p, string caption, GMarkerGoogleType type)
: base(p)
{
font = new Font("Arial", 14);
innerMarker = new GMarkerGoogle(p, type);
Caption = caption;
}
public override void OnRender(Graphics g)
{
if (innerMarker != null)
{
innerMarker.OnRender(g);
}
g.DrawString(Caption, font, Brushes.Black, new PointF(0.0f, innerMarker.Size.Height));
}
public override void Dispose()
{
if(innerMarker != null)
{
innerMarker.Dispose();
innerMarker = null;
}
base.Dispose();
}
#region ISerializable Members
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
}
protected GmapMarkerWithLabel(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
#endregion
}
Usage (assuming a GMap instance named gm):
GMapOverlay markerOverlay = new GMapOverlay("markers");
gm.Overlays.Add(markerOverlay);
var labelMarker = new GmapMarkerWithLabel(new PointLatLng(53.3, 9), "caption text", GMarkerGoogleType.blue);
markerOverlay.Markers.Add(labelMarker)
I'll answer here because this is the first question that pops up when looking to display a text marker for the WPF GMAP.NET library. Displaying a text marker with the WPF version of the library is actually much easier than in WinForms, or at least than the accepted answer.
The GMapMarker in WPF has a Shape property of type UIElement, which means you can provide a System.Windows.Controls.TextBlock object to display a text marker :
Markers.Add(new GMapMarker(new PointLatLng(latitude, longitude))
{
Shape = new System.Windows.Controls.TextBlock(new System.Windows.Documents.Run("Label"))
});
Since the marker displays the top left portion of the shape at the given position, you can play with the GMapMarker.Offset property to adjust the text position according to its dimensions. For instance, if you want the text to be horizontally centered on the marker's position :
var textBlock = new TextBlock(new Run("Label"));
textBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
textBlock.Arrange(new Rect(textBlock.DesiredSize));
Markers.Add(new GMapMarker(new PointLatLng(request.Latitude, request.Longitude))
{
Offset = new Point(-textBlock.ActualWidth / 2, 0),
Shape = textBlock
});
The solution to get the TextBlock's dimensions was quickly taken from this question, so if you need a more accurate way of getting the block's dimensions to play with the offset I suggest you start from there.

An efficient way to paint gradient rectangles

I'm generating a bunch of RectangleF objects having different sizes and positions. What would be the best way to fill them with a gradient Brush in GDI+?
In WPF I could create a LinearGradientBrush, set Start and End relative points and WPF would take care of the rest.
In GDI+ however, the gradient brush constructor requires the position in absolute coordinates, which means I have to create a Brush for each of the rectangle, which would be a very complex operation.
Am I missing something or that's indeed the only way?
You can specify a transform at the moment just before the gradient is applied if you would like to declare the brush only once. Note that using transformations will override many of the constructor arguments that can be specified on a LinearGradientBrush.
LinearGradientBrush.Transform Property (System.Drawing.Drawing2D)
To modify the transformation, call the methods on the brush object corresponding to the desired matrix operations. Note that matrix operations are not commutative, so order is important. For your purposes, you'll probably want to do them in this order for each rendition of your rectangles: Scale, Rotate, Offset/Translate.
LinearGradientBrush.ResetTransform Method # MSDN
LinearGradientBrush.ScaleTransform Method (Single, Single, MatrixOrder) # MSDN
LinearGradientBrush.RotateTransform Method (Single, MatrixOrder) # MSDN
LinearGradientBrush.TranslateTransform Method (Single, Single, MatrixOrder) # MSDN
Note that the system-level drawing tools don't actually contain a stock definition for gradient brush, so if you have performance concerns about making multiple brushes, creating a multitude of gradient brushes shouldn't cost any more than the overhead of GDI+/System.Drawing maintaining the data required to define the gradient and styling. You may be just as well off to create a Brush per rectangle as needed, without having to dive into the math required to customize the brush via transform.
Brush Functions (Windows) # MSDN
Here is a code example you can test in a WinForms app. This app paints tiles with a gradient brush using a 45 degree gradient, scaled to the largest dimension of the tile (naively calculated). If you fiddle with the values and transformations, you may find that it isn't worth using the technique setting a transform for all of your rectangles if you have non-trivial gradient definitions. Otherwise, remember that your transformations are applied at the world-level, and in the GDI world, the y-axis is upside down, whereas in the cartesian math world, it is ordered bottom-to-top. This also causes the angle to be applied clockwise, whereas in trigonometry, the angle progresses counter-clockwise in increasing value for a y-axis pointing up.
using System.Drawing.Drawing2D;
namespace TestMapTransform
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Rectangle rBrush = new Rectangle(0,0,1,1);
Color startColor = Color.DarkRed;
Color endColor = Color.White;
LinearGradientBrush br = new LinearGradientBrush(rBrush, startColor, endColor, LinearGradientMode.Horizontal);
int wPartitions = 5;
int hPartitions = 5;
int w = this.ClientSize.Width;
w = w - (w % wPartitions) + wPartitions;
int h = this.ClientSize.Height;
h = h - (h % hPartitions) + hPartitions;
for (int hStep = 0; hStep < hPartitions; hStep++)
{
int hUnit = h / hPartitions;
for (int wStep = 0; wStep < wPartitions; wStep++)
{
int wUnit = w / wPartitions;
Rectangle rTile = new Rectangle(wUnit * wStep, hUnit * hStep, wUnit, hUnit);
if (e.ClipRectangle.IntersectsWith(rTile))
{
int maxUnit = wUnit > hUnit ? wUnit : hUnit;
br.ResetTransform();
br.ScaleTransform((float)maxUnit * (float)Math.Sqrt(2d), (float)maxUnit * (float)Math.Sqrt(2d), MatrixOrder.Append);
br.RotateTransform(45f, MatrixOrder.Append);
br.TranslateTransform(wUnit * wStep, hUnit * hStep, MatrixOrder.Append);
e.Graphics.FillRectangle(br, rTile);
br.ResetTransform();
}
}
}
}
private void Form1_Resize(object sender, EventArgs e)
{
this.Invalidate();
}
}
}
Here's a snapshot of the output:
I recommend you to create a generic method like this:
public void Paint_rectangle(object sender, PaintEventArgs e)
{
RectangleF r = new RectangleF(0, 0, e.ClipRectangle.Width, e.ClipRectangle.Height);
if (r.Width > 0 && r.Height > 0)
{
Color c1 = Color.LightBlue;
Color c2 = Color.White;
Color c3 = Color.LightBlue;
LinearGradientBrush br = new LinearGradientBrush(r, c1, c3, 90, true);
ColorBlend cb = new ColorBlend();
cb.Positions = new[] { 0, (float)0.5, 1 };
cb.Colors = new[] { c1, c2, c3 };
br.InterpolationColors = cb;
// paint
e.Graphics.FillRectangle(br, r);
}
}
then, for every rectangle just call:
yourrectangleF.Paint += new PaintEventHandler(Paint_rectangle);
If the gradrients colors are all the same, you can make that method shorter. Hope that helped..

How do I get the current mouse screen coordinates in WPF?

How to get current mouse coordination on the screen?
I know only Mouse.GetPosition() which get mousePosition of element, but I want to get the coordination without using element.
Or in pure WPF use PointToScreen.
Sample helper method:
// Gets the absolute mouse position, relative to screen
Point GetMousePos() => _window.PointToScreen(Mouse.GetPosition(_window));
To follow up on Rachel's answer.
Here's two ways in which you can get Mouse Screen Coordinates in WPF.
1.Using Windows Forms. Add a reference to System.Windows.Forms
public static Point GetMousePositionWindowsForms()
{
var point = Control.MousePosition;
return new Point(point.X, point.Y);
}
2.Using Win32
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(ref Win32Point pt);
[StructLayout(LayoutKind.Sequential)]
internal struct Win32Point
{
public Int32 X;
public Int32 Y;
};
public static Point GetMousePosition()
{
var w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
return new Point(w32Mouse.X, w32Mouse.Y);
}
Do you want coordinates relative to the screen or the application?
If it's within the application just use:
Mouse.GetPosition(Application.Current.MainWindow);
If not, I believe you can add a reference to System.Windows.Forms and use:
System.Windows.Forms.Control.MousePosition;
If you try a lot of these answers out on different resolutions, computers with multiple monitors, etc. you may find that they don't work reliably. This is because you need to use a transform to get the mouse position relative to the current screen, not the entire viewing area which consists of all your monitors. Something like this...(where "this" is a WPF window).
var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice;
var mouse = transform.Transform(GetMousePosition());
public System.Windows.Point GetMousePosition()
{
var point = Forms.Control.MousePosition;
return new Point(point.X, point.Y);
}
This works without having to use forms or import any DLLs:
using System.Windows;
using System.Windows.Input;
/// <summary>
/// Gets the current mouse position on screen
/// </summary>
private Point GetMousePosition()
{
// Position of the mouse relative to the window
var position = Mouse.GetPosition(Window);
// Add the window position
return new Point(position.X + Window.Left, position.Y + Window.Top);
}
You may use combination of TimerDispatcher (WPF Timer analog) and Windows "Hooks" to catch cursor position from operational system.
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetCursorPos(out POINT pPoint);
Point is a light struct. It contains only X, Y fields.
public MainWindow()
{
InitializeComponent();
DispatcherTimer dt = new System.Windows.Threading.DispatcherTimer();
dt.Tick += new EventHandler(timer_tick);
dt.Interval = new TimeSpan(0,0,0,0, 50);
dt.Start();
}
private void timer_tick(object sender, EventArgs e)
{
POINT pnt;
GetCursorPos(out pnt);
current_x_box.Text = (pnt.X).ToString();
current_y_box.Text = (pnt.Y).ToString();
}
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
This solution is also resolving the problem with too often or too infrequent parameter reading so you can adjust it by yourself. But remember about WPF method overload with one arg which is representing ticks not milliseconds.
TimeSpan(50); //ticks
If you're looking for a 1 liner, this does well.
new Point(Mouse.GetPosition(mWindow).X + mWindow.Left, Mouse.GetPosition(mWindow).Y + mWindow.Top)
The + mWindow.Left and + mWindow.Top makes sure the position is in the right place even when the user drags the window around.
Mouse.GetPosition(mWindow) gives you the mouse position relative to the parameter of your choice.
mWindow.PointToScreen() convert the position to a point relative to the screen.
So mWindow.PointToScreen(Mouse.GetPosition(mWindow)) gives you the mouse position relative to the screen, assuming that mWindow is a window(actually, any class derived from System.Windows.Media.Visual will have this function), if you are using this inside a WPF window class, this should work.
I wanna use this code
Point PointA;
private void Button_PreviewMouseUp(object sender, MouseButtonEventArgs e) {
PointA = e.MouseDevice.GetPosition(sender as UIElement);
}
private void Button_Click(object sender, RoutedEventArgs e) {
// use PointA Here
}

Creating SelectionBorder: Bit in the face by decimal rounding?

I am currently implementing a class called SelectionBorder in WPF. It's derived from the Shape class.
It basically looks like this:
public class SelectionBorder : Shape
{
public Point StartPoint {get; set;}
public PointCollection Points {get; set;}
public double StrokeLength {get; set;}
protected override Geometry DefiningGeometry{
get{
//Magic!
}
}
}
The StartPoint and Points properties determine the corners of the border. The border is a typical stroked line border (one black stroke, one invisible stroke like that: - - - -)
The problem that I have now is that since the corner points are freely choosable it's pretty common that the count of strokes (meaning black and invisible strokes) is not even (in fact not even an integer) and therefore the first stroke looks longer than the others (visible in the picture). This maybe doesn't seem to be a big deal but I later want to animate the border so that the strokes circle round the content. When doing this animation the tiny flaw in the static view becomes clearly visible and in my opinion is highly disturbing.
alt text http://img14.imageshack.us/img14/2874/selectionborder.png
The problem is that I tried to determine a StrokeLength that gets as close to the original StrokeLength as possible and creates an even number of strokes. However the problem I've run into is that WPF (obviously) can't display the whole precision of a double decimal StrokeLength and therefore the resulting stroke number is uneven once again.
Is there any workaround for this problem? Do you probably have another solution for my problem?
Thanks in advance!
EDIT: I retested and reviewed the code after a little break for fitness today and after all it happens only on very big StrokeLengths. I plan to use StrokeLengths of 2 where the little animation jumping does matter much less than I originally thought.
You could make more than one corner "un-matched" in that regard. For example, instead of having one point be the "source" and "destination" of the animated dashes, you could pick 2 points. One would be the "source", dashes appearing to march away from it in 2 directions, and another point be the "destination", where dashes converge and disappear.
GIMP, for example, animates selection dashed lines in this way and seems to pick a point closest to the lower-left for the "source" and a point closest to the upper-right for the "destination".
You could come up with some other scheme, as well.
Just remember that while it may look disturbing to you, most users will not care.
I just found a way that makes it way easier to create such an animated SelectionBorder.
Instead of creating the animation by moving an self-created AnimationPoint through animation I just animated the StrokeDashOffset property natively provided by the Shape class and setting the StrokeDashArray to define the StrokeLength.
It would look like this in XAML:
<namespace:SelectionBorder StrokeDashArray="2" AnimationDuration="0:0:1" Stroke="Black" />
The class looks like this:
public class SelectionBorder : Shape
{
private DoubleAnimation m_Animation;
private bool m_AnimationStarted;
public SelectionBorder()
{
IsVisibleChanged += OnIsVisibleChanged;
}
protected void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (Visibility == Visibility.Visible)
{
StartAnimation();
}
else
{
StopAnimation();
}
}
public void StartAnimation()
{
if (m_AnimationStarted)
return;
if (m_Animation == null)
{
m_Animation = CreateAnimation();
}
BeginAnimation(StrokeDashOffsetProperty, m_Animation);
m_AnimationStarted = true;
}
protected virtual DoubleAnimation CreateAnimation()
{
DoubleAnimation animation = new DoubleAnimation();
animation.From = 0;
if (StrokeDashArray.Count == 0)
animation.To = 4;
else
animation.To = StrokeDashArray.First() * 2;
animation.Duration = AnimationDuration;
animation.RepeatBehavior = RepeatBehavior.Forever;
return animation;
}
public void StopAnimation()
{
if (m_AnimationStarted)
{
BeginAnimation(StrokeDashOffsetProperty, null);
m_AnimationStarted = false;
}
}
#region Dependency Properties
public Duration AnimationDuration
{
get { return (Duration)GetValue(AnimationDurationProperty); }
set { SetValue(AnimationDurationProperty, value); }
}
public static readonly DependencyProperty AnimationDurationProperty =
DependencyProperty.Register("AnimationDuration", typeof(Duration), typeof(SelectionBorder), new UIPropertyMetadata(new Duration(TimeSpan.FromSeconds(0.5))));
#endregion Dependency Properties
protected override Geometry DefiningGeometry
{
get
{
double width = (double.IsNaN(Width)) ? ((Panel)Parent).ActualWidth : Width;
double height = (double.IsNaN(Height)) ? ((Panel)Parent).ActualHeight : Height;
RectangleGeometry geometry = new RectangleGeometry(new Rect(0, 0, width, height));
return geometry;
}
}
}

Selecting an object on a WPF Canvas?

I have a WPF Canvas with some Ellipse objects on it (displayed as circles). Each circle is from a collection class instance which is actually a custom hole pattern class. Each pattern has a certain number of circles, and each circle then gets added to the canvas using an iteration over the collection using the code below.
So, the canvas is populated with a bunch of circles and each circle belongs to a certain pattern instance. You can see a screenshot here: http://twitpic.com/1f2ci/full
Now I want to add the ability to click on a circle on the canvas, and be able to determine the collection it belongs to, so that I can then do some more work on the selected pattern to which that circle belongs.
public void DrawHoles()
{
// Iterate over each HolePattern in the HolePatterns collection...
foreach (HolePattern HolePattern in HolePatterns)
{
// Now iterate over each Hole in the HoleList of the current HolePattern...
// This code adds the HoleEntity, HoleDecorator, and HoleLabel to the canvas
foreach (Hole Hole in HolePattern.HoleList)
{
Hole.CanvasX = SketchX0 + (Hole.AbsX * _ZoomScale);
Hole.CanvasY = SketchY0 - (Hole.AbsY * _ZoomScale);
canvas1.Children.Add(Hole.HoleEntity);
}
}
}
All FrameworkElements have a Tag property which is of type object that can be used to hold arbitrary information. You could assign the HolePattern to the Tag property and easily use that later to get the associated collection.
i.e.:
...
Hole.HoleEntity.Tag = HolePattern as object;
canvas1.Children.Add(Hole.HoleEntity);
later on in the click event:
event(object sender,....)
{
Ellipse e = sender as Ellipse;
HolePattern hp = e.Tag as HolePattern;
...
}
So you probably already read my reply where I said I had it working. And it does work perfectly, (except that it requires great precision with the mouse), but I want to ask this: is it really smart to add an event handler to EVERY ellipse that gets added to a canvas? Now I don't know what kind of memory bog that could be, or maybe it is a piece of cake for WPF and Windows to handle.
In a practical case, I guess there would be not more that 30-50 holes even on a screen that had multiple patterns, but still; FIFTY event handlers? It just seems scary. And actually, each "Hole" is visually represented by two concentric circles and a text label (see the screenshow here: http://twitpic.com/1f2ci/full ), and I know the user would expect to be able to click on any one of those elements to select a hole. That means an event handler on 3 elements for every hole. Now we could be talking about 100 or more event handlers.
It seems like there should be a solution where you could have just one event handler on the Canvas and read the element reference under the mouse, then work off of that to get the .Tag property of that elment, and so on.
I thought I'd post my final and more refined solution in case it helps anyone else.
void canvas1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
int ClickMargin = 2;// Adjust here as desired. Span is in both directions of selected point.
var ClickMarginPointList = new Collection<Point>();
Point ClickedPoint = e.GetPosition(canvas1);
Point ClickMarginPoint=new Point();
for (int x = -1 * ClickMargin; x <= ClickMargin; x++)
{
for (int y = -1 * ClickMargin; y <= ClickMargin; y++)
{
ClickMarginPoint.X = ClickedPoint.X + x;
ClickMarginPoint.Y = ClickedPoint.Y + y;
ClickMarginPointList.Add(ClickMarginPoint);
}
}
foreach (Point p in ClickMarginPointList)
{
HitTestResult SelectedCanvasItem = System.Windows.Media.VisualTreeHelper.HitTest(canvas1, p);
if (SelectedCanvasItem.VisualHit.GetType().BaseType == typeof(Shape))
{
var SelectedShapeTag = SelectedCanvasItem.VisualHit.GetValue(Shape.TagProperty);
if (SelectedShapeTag!=null && SelectedShapeTag.GetType().BaseType == typeof(Hole))
{
Hole SelectedHole = (Hole)SelectedShapeTag;
SetActivePattern(SelectedHole.ParentPattern);
SelectedHole.ParentPattern.CurrentHole = SelectedHole;
return; //Get out, we're done.
}
}
}
}

Resources