WPF is horizontaloffset of scrollviewer accurate at large values? - c#

I've decided to repose this question with the full code. I created a test case just demonstrating what i think is the weird behavior so that other people can run the code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
namespace Test
{
public class TestSizeVisual: FrameworkElement
{
public double MinimumXInDIPs
{
get { return (double)GetValue(MinimumXInDIPsProperty); }
set
{
SetValue(MinimumXInDIPsProperty, value);
}
}
public static readonly DependencyProperty MinimumXInDIPsProperty =
DependencyProperty.Register("MinimumXInDIPs",
typeof(double), typeof(TestSizeVisual),
new FrameworkPropertyMetadata(new double(), new PropertyChangedCallback(Redraw)));
public double ViewportWidth
{
get { return (double)GetValue(ViewportWidthProperty); }
set
{
SetValue(ViewportWidthProperty, value);
}
}
public static readonly DependencyProperty ViewportWidthProperty =
DependencyProperty.Register("ViewportWidth",
typeof(double), typeof(TestSizeVisual),
new FrameworkPropertyMetadata(new double(), FrameworkPropertyMetadataOptions.AffectsMeasure));
VisualCollection visuals;
protected override Size MeasureOverride(Size availableSize)
{
return new Size(50000000000, 50);
}
public TestSizeVisual()
{
visuals = new VisualCollection(this);
this.Loaded += new RoutedEventHandler(TestSizeVisual_Loaded);
}
void TestSizeVisual_Loaded(object sender, RoutedEventArgs e)
{
DrawSignal();
}
private static void Redraw(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TestSizeVisual sg = d as TestSizeVisual;
if (sg != null && sg.IsLoaded)
{
sg.DrawSignal();
}
}
private void DrawSignal()
{
DrawingVisual signalbox = new DrawingVisual();
using (DrawingContext dc = signalbox.RenderOpen())
{
dc.DrawRectangle(Brushes.Orange, new Pen(Brushes.Black, 2), new Rect(new Point(0, 0), new Point(1000000000, ActualHeight)));
dc.DrawRectangle(Brushes.Purple, new Pen(Brushes.Black, 2), new Rect(new Point(MinimumXInDIPs, 2), new Point(MinimumXInDIPs + 10, 6)));
dc.DrawRectangle(Brushes.Purple, new Pen(Brushes.Black, 2), new Rect(new Point(MinimumXInDIPs + ViewportWidth - 10, 2), new Point(MinimumXInDIPs + ViewportWidth, 6)));
dc.DrawLine(new Pen(Brushes.Black, 2), new Point(MinimumXInDIPs, 10),
new Point(MinimumXInDIPs + ViewportWidth, 10));
}
visuals.Add(signalbox);
InvalidateVisual();
}
protected override Visual GetVisualChild(int index)
{
return visuals[index];
}
protected override int VisualChildrenCount
{
get
{
return visuals.Count;
}
}
}
}
The xaml file looks like this:
<ScrollViewer
x:Name="scrollviewer1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible"
>
<test:TestSizeVisual
MinimumXInDIPs="{Binding ElementName=scrollviewer1, Path=HorizontalOffset}"
ViewportWidth="{Binding ElementName=scrollviewer1, Path=ViewportWidth}"
/>
</ScrollViewer>
The rectangles and lines stay centered on screen as you scroll for small sizes of the testsizevisual. However, once I alter the measureoverride to return a huge size, scrolling no longer results in correct centering of the drawings. Why does everything glitch at huge sizes? Do I need to code my own scrollviewer to accomodate larger amounts of content or is there a workaround?

So a friend finally figured this out for me. Even though i was doing calculations with doubles, WPF does drawing using direct x. Direct x is limited by floats. By checking the mantissa of the float on wiki, i found that I was exceeding the value of the mantissa for a float, thus the value was no longer accurate.

Related

Updating GMap.NET Marker Image in C#

I can change my markers position with
markers.Markers[2].Position = new PointLatLng(30.0000, 30.00000);
but how can i change marker icon with setting a varible like above?
I am declaring points as
GMap.NET.WindowsForms.GMapMarker marker3 =
new GMap.NET.WindowsForms.Markers.GMarkerGoogle(
new GMap.NET.PointLatLng(30.0000, 30.00000),
new Bitmap("images/2.png"));
thanks...
To solve this problem, i contacted the creator of the library: radioman.
He referred some code called 'GMarkerArrow.' Here is the code:
namespace Demo.WindowsForms.CustomMarkers
{
using System;
using System.Drawing;
using System.Runtime.Serialization;
using GMap.NET;
using GMap.NET.WindowsForms;
[Serializable]
public class GMarkerArrow : GMapMarker, ISerializable
{
[NonSerialized]
public Brush Fill = new SolidBrush(Color.FromArgb(155, Color.Blue));
//[NonSerialized]
//public Pen Pen = new Pen(Brushes.Blue, 5);
static readonly Point[] Arrow = new Point[] { new Point(-7, 7), new Point(0, -22), new Point(7, 7), new Point(0, 2) };
public float Bearing = 0;
private float scale = 1;
public float Scale
{
get
{
return scale;
}
set
{
scale = value;
Size = new System.Drawing.Size((int)(14 * scale), (int)(14 * scale));
Offset = new System.Drawing.Point(-Size.Width / 2, (int)(-Size.Height / 1.4));
}
}
public GMarkerArrow(PointLatLng p)
: base(p)
{
Scale = (float)1.4;
}
public override void OnRender(Graphics g)
{
//g.DrawRectangle(Pen, new System.Drawing.Rectangle(LocalPosition.X, LocalPosition.Y, Size.Width, Size.Height));
{
g.TranslateTransform(ToolTipPosition.X, ToolTipPosition.Y);
var c = g.BeginContainer();
{
g.RotateTransform(Bearing - Overlay.Control.Bearing);
g.ScaleTransform(Scale, Scale);
g.FillPolygon(Fill, Arrow);
}
g.EndContainer(c);
g.TranslateTransform(-ToolTipPosition.X, -ToolTipPosition.Y);
}
}
public override void Dispose()
{
//if(Pen != null)
//{
// Pen.Dispose();
// Pen = null;
//}
if (Fill != null)
{
Fill.Dispose();
Fill = null;
}
base.Dispose();
}
#region ISerializable Members
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
}
protected GMarkerArrow(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
#endregion
}
}
Save this as GMarkerArrow.cs and add it to your project.
Add this to your Form1 code:
using Demo.WindowsForms.CustomMarkers;
Now you can use the new marker via following:
GMarkerArrow marker1 = new GMarkerArrow(new PointLatLng(-30, -40));
marker1.ToolTipText = "blablabla";
marker1.ToolTip.Fill = Brushes.Black;
marker1.ToolTip.Foreground = Brushes.White;
marker1.ToolTip.Stroke = Pens.Black;
marker1.Bearing = 30; // Rotation angle
marker1.Fill = new SolidBrush(Color.FromArgb(155, Color.Blue)); // Arrow color
markers.Markers.Add(marker1);
gMapControl1.Overlays.Add(markers);
Also i want to thanks #rdoubleui , thanks you sir.
I hope this helps to everyone.
GMapOverlay markersOverlay;
private void AddOrUpdateMarker(string tag, double lat, double lng, Image NewImage)
{
Bitmap bmpmarker = (Bitmap)NewImage;
var marker = markersOverlay.Markers.FirstOrDefault(m => m.Tag == tag);
if(marker!=null)
{
markersOverlay.Markers.Remove(marker);
gMapControl1.Refresh();
marker = new GMarkerGoogle(new PointLatLng(lat, lng), bmpmarker);
marker.Tag = tag;
markersOverlay.Markers.Add(marker);
gMapControl1.Refresh();
}
else
{
marker = new GMarkerGoogle(new PointLatLng(lat, lng), bmpmarker);
marker.Tag = tag;
markersOverlay.Markers.Add(marker);
gMapControl1.Refresh();
}
}
The problem updating the image of that marker is that the Image property is not publicly accessible, therefore you can not update the image that way.
There are two possibilities: first one is to replace the marker reference with a new one giving you the opportunity to set a new image and copying the position of the current marker. However that is not the clean way as you unnecessarily create a whole bunch of references only to dispose them right away depending on the use case. If it's a one-time update, then this approach is fine.
The preferable way is to derive from GMapMarker as the Google marker does (you can use that as a template, leaving out the whole google specific icon logic). Are you familiar with the concept of deriving? It will require some more effort but will be worth it, could help with that.
Also your main reference is probably the project's github page.
EDIT
using System.Drawing;
public class GImageMarker : GMapMarker
{
public Bitmap Bitmap { get; set; }
public GImageMarker(PointLatLng p, Bitmap Bitmap)
: base(p)
{
this.Bitmap = Bitmap;
Size = new System.Drawing.Size(Bitmap.Width, Bitmap.Height);
Offset = new Point(-Size.Width / 2, -Size.Height);
}
public override void OnRender(Graphics g)
{
g.DrawImage(Bitmap, LocalPosition.X, LocalPosition.Y, Size.Width, Size.Height);
}
}

c# uwp GridView custom panel

I'm still struggling with my custom panel for my GridView (see other question: UWP c# Binding to ItemsPanel / Custom Panel (Template))
I have set up my GridView with my custom panel. Now if I switch my GridView Selection Mode from 'Multiple' to 'Single' and vice-versa, the items in the custom panels are not updated automatically => the 'selection square' doesn't show/hide for all items at once, only if I runs my mouse over it.
This doesn't happe if I use an ItemsWrapGrid for instance... Which Event /Method do I need to implement in the custom panel?
So far my code of the panel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using teachers_cal;
using teachers_cal.Classes;
namespace teachers_cal.UserControls
{
public class PRGD010_GridViewPanel : Panel
{
public int NumberRowsOrColumns
{
get { return (int)GetValue(NumberRowsOrColumnsProperty); }
set { SetValue(NumberRowsOrColumnsProperty, value < 1 ? 0 : value); }
}
public static readonly DependencyProperty NumberRowsOrColumnsProperty = DependencyProperty.Register("NumberRowsOrColumns", typeof(int), typeof(PRGD010_GridViewPanel), new PropertyMetadata(1, OnNumberRowsOrColumnsPropertyChanged));
private static void OnNumberRowsOrColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
(source as PRGD010_GridViewPanel).InvalidateMeasure();
}
public int Offset
{
get { return (int)GetValue(StartPositionProperty); }
set { SetValue(StartPositionProperty, value >= this.NumberRowsOrColumns ? this.NumberRowsOrColumns - 1 : value); }
}
public static readonly DependencyProperty StartPositionProperty = DependencyProperty.Register("Offset", typeof(int), typeof(PRGD010_GridViewPanel), new PropertyMetadata(0, OnStartPositionPropertyChanged));
private static void OnStartPositionPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
(source as PRGD010_GridViewPanel).InvalidateMeasure();
}
public PRGD010_GridViewPanel()
{
}
/// Get Items Source count items
private int GetItemsCount()
{
return this.Children != null ? this.Children.Count : 0;
}
/// Get Calculated Row or Column count. Depending on Offset, Numbers and Items
private int GetRowsOrColumnsCount()
{
int additionalLine = ((GetItemsCount() + this.Offset)) % this.NumberRowsOrColumns > 0 ? 1 : 0;
return ((GetItemsCount() + this.Offset)) / this.NumberRowsOrColumns + additionalLine;
}
/// <summary>
/// Arrange all items
/// </summary>
protected override Size ArrangeOverride(Size finalSize)
{
// Clip to ensure items dont override container
this.Clip = new RectangleGeometry { Rect = new Rect(0, 0, finalSize.Width, finalSize.Height) };
Double positionTop = 0d;
double positionGrid = this.Offset;
double test = this.NumberRowsOrColumns;
// Must Create looping items count
foreach (UIElement item in this.Children)
{
if (item == null)
continue;
Size desiredSize = item.DesiredSize;
if (double.IsNaN(desiredSize.Width) || double.IsNaN(desiredSize.Height)) continue;
// Get rect position
var rect = new Rect(positionGrid * desiredSize.Width, positionTop, desiredSize.Width, desiredSize.Height);
item.Arrange(rect);
// set internal CompositeTransform to handle movement
TranslateTransform compositeTransform = new TranslateTransform();
item.RenderTransform = compositeTransform;
if (positionGrid < this.NumberRowsOrColumns - 1) { positionGrid += 1; }
else
{
positionGrid = 0;
positionTop += desiredSize.Height;
}
}
return finalSize;
}
/// <summary>
/// Measure items
/// </summary>
protected override Size MeasureOverride(Size availableSize)
{
//Size s = base.MeasureOverride(availableSize);
// set good cliping
//this.Clip = new RectangleGeometry { Rect = new Rect(0, 0, s.Width, s.Height) };
// Measure all items
foreach (UIElement container in this.Children)
{
container.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
}
double width = Children[0]?.DesiredSize.Width ?? 0;
double height = Children[0]?.DesiredSize.Height ?? 0;
var i = GetRowsOrColumnsCount();
return (new Size(width * (double)NumberRowsOrColumns, height * (double)GetRowsOrColumnsCount()));
//return (new Size(3000, 3000));
}
}
}
EDIT:
I've created two simple projects. Even with standard ItemPanels I got strange behavior. It's really frustrating
Using the GridView with a VariableSizeWrapGrid the GridViewItems will not be refresh properly when I change SelectionMode
Doing the same with ItemsWrapGrip is okay. Changing SelectionMode will be displayed correctly...
Could someone figure out why this happens? And what to do?!
See the two links...
https://onedrive.live.com/redir?resid=4305F58623B1701D!42317&authkey=!APnPBY_9SNDS4AI&ithint=file%2czip
https://onedrive.live.com/redir?resid=4305F58623B1701D!42316&authkey=!AMDZtSBQ5bjWaoU&ithint=file%2czip
Thanks for your reply

How to display a control which (transparent) BackColor doesn't cover below controls?

I have a custom Control:
public class Temp : Control
{
public Temp(Color col, int x, int y)
{
Size = new Size(x + 10, y + 10);
this.x = x;
this.y = y;
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
BackColor = col;
}
int x, y;
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (var p = new Pen(Color.Black, 3))
{
e.Graphics.DrawLine(p, new Point(10, 10), new Point(x, y));
}
}
}
And from the Load event of my Form I add two of these control to the Controls of a Panel I added as the only control of my Form:
panel1.Controls.Add(new Temp(Color.Red, 50, 50));
panel1.Controls.Add(new Temp(Color.Violet, 10, 100));
This is the output:
As you can see the first control cover the second one, while I'd want to display only the two lines, where the controls background color is transparent.
Note that using a transparent BackColor doesn't work:
panel1.Controls.Add(new Temp(Color.Transparent, 50, 50));
panel1.Controls.Add(new Temp(Color.Violet, 10, 100));
And this is the output:
How can I solve this problem? That is, display only (and completely) both my lines?
Instead of Inheriting from Control, create two points in your custom class.
Sample code given below
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace SOWinForm
{
public partial class Form1 : Form
{
List<Line> lines;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
lines = new List<Line>();
lines.Add(new Line(){ StartPoint = new Point(10,10), EndPoint = new Point(10,100)});
lines.Add(new Line() { StartPoint = new Point(10, 10), EndPoint = new Point(50, 50) });
}
protected override void OnPaint(PaintEventArgs e)
{
foreach (var line in lines)
{
using (var p = new Pen(Color.Black, 3))
{
e.Graphics.DrawLine(p, line.StartPoint, line.EndPoint);
}
}
}
}
public class Line
{
public Point StartPoint {get;set;}
public Point EndPoint { get; set; }
//Add Custom Properties
}
}
When you are setting transparent backcolor that doesn't mean that the background color is transparent but the color of its parent. The parent of first control is the panel(with gray color) so also the color of the control is gray. Set the parent of the first control to be the second one.
valter

How can I show a Balloon Tip over a textbox?

I have a C# WPF application using XAML and MVVM. My question is: How can I show a balloon tooltip above a text box for some invalid data entered by the user?
I want to use Microsoft's native balloon control for this. How would I implement this into my application?
Just add a reference to System.Windows.Forms and C:\Program Files\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\WindowsFormsIntegration.dll
and then:
WindowsFormsHost host =new WindowsFormsHost();
var toolTip1 = new System.Windows.Forms.ToolTip();
toolTip1.AutoPopDelay = 5000;
toolTip1.InitialDelay = 1000;
toolTip1.ReshowDelay = 500;
toolTip1.ShowAlways = true;
toolTip1.IsBalloon = true;
toolTip1.ToolTipIcon = System.Windows.Forms.ToolTipIcon.Info;
toolTip1.ToolTipTitle = "Title:";
System.Windows.Forms.TextBox tb = new System.Windows.Forms.TextBox();
tb.Text="Go!";
toolTip1.SetToolTip(tb, "My Info!");
host.Child = tb;
grid1.Children.Add(host); //a container for windowsForm textBox
and this is a sample for WinForm ToolTip Ballon in WPF:
Hope this help!
This BalloonDecorator Project is one that I am using on a current project to show help hints and error notifications. I know you could modify your error template to show this decorator, just like you could show an icon instead of the red borders. The benefit of using a decorator is you can make it look however you'd like, and won't have to depend on WinForms.
BalloonDecorator.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace MyNamespace
{
public class BalloonDecorator : Decorator
{
private static double _thickness = 0;
private static int OpeningGap = 10;
public static readonly DependencyProperty BackgroundProperty =
DependencyProperty.Register("Background", typeof (Brush), typeof (BalloonDecorator));
public static readonly DependencyProperty BorderBrushProperty =
DependencyProperty.Register("BorderBrush", typeof (Brush), typeof (BalloonDecorator));
public static readonly DependencyProperty PointerLengthProperty =
DependencyProperty.Register("PointerLength", typeof (double), typeof (BalloonDecorator),
new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure));
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof (double), typeof (BalloonDecorator),
new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure));
public Brush Background
{
get { return (Brush) GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
}
public Brush BorderBrush
{
get { return (Brush) GetValue(BorderBrushProperty); }
set { SetValue(BorderBrushProperty, value); }
}
public double PointerLength
{
get { return (double) GetValue(PointerLengthProperty); }
set { SetValue(PointerLengthProperty, value); }
}
public double CornerRadius
{
get { return (double) GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
protected override Size ArrangeOverride(Size arrangeSize)
{
UIElement child = Child;
if (child != null)
{
double pLength = PointerLength;
Rect innerRect =
Rect.Inflate(new Rect(pLength, 0, Math.Max(0, arrangeSize.Width - pLength), arrangeSize.Height),
-1 * _thickness, -1 * _thickness);
child.Arrange(innerRect);
}
return arrangeSize;
}
protected override Size MeasureOverride(Size constraint)
{
UIElement child = Child;
Size size = new Size();
if (child != null)
{
Size innerSize = new Size(Math.Max(0, constraint.Width - PointerLength), constraint.Height);
child.Measure(innerSize);
size.Width += child.DesiredSize.Width;
size.Height += child.DesiredSize.Height;
}
Size borderSize = new Size(2 * _thickness, 2 * _thickness);
size.Width += borderSize.Width + PointerLength;
size.Height += borderSize.Height;
return size;
}
protected override void OnRender(DrawingContext dc)
{
Rect rect = new Rect(0, 0, RenderSize.Width, RenderSize.Height);
dc.PushClip(new RectangleGeometry(rect));
dc.DrawGeometry(Background, new Pen(BorderBrush, _thickness), CreateBalloonGeometry(rect));
dc.Pop();
}
private StreamGeometry CreateBalloonGeometry(Rect rect)
{
double radius = Math.Min(CornerRadius, rect.Height / 2);
double pointerLength = PointerLength;
// All the points on the path
Point[] points =
{
new Point(pointerLength + radius, 0), new Point(rect.Width - radius, 0), // Top
new Point(rect.Width, radius), new Point(rect.Width, rect.Height - radius), // Right
new Point(rect.Width - radius, rect.Height), // Bottom
new Point(pointerLength + radius, rect.Height), // Bottom
new Point(pointerLength, rect.Height - radius), // Left
new Point(pointerLength, radius) // Left
};
StreamGeometry geometry = new StreamGeometry();
geometry.FillRule = FillRule.Nonzero;
using (StreamGeometryContext ctx = geometry.Open())
{
ctx.BeginFigure(points[0], true, true);
ctx.LineTo(points[1], true, false);
ctx.ArcTo(points[2], new Size(radius, radius), 0, false, SweepDirection.Clockwise, true, false);
ctx.LineTo(points[3], true, false);
ctx.ArcTo(points[4], new Size(radius, radius), 0, false, SweepDirection.Clockwise, true, false);
ctx.LineTo(points[5], true, false);
ctx.ArcTo(points[6], new Size(radius, radius), 0, false, SweepDirection.Clockwise, true, false);
// Pointer
if (pointerLength > 0)
{
ctx.LineTo(rect.BottomLeft, true, false);
ctx.LineTo(new Point(pointerLength, rect.Height - radius - OpeningGap), true, false);
}
ctx.LineTo(points[7], true, false);
ctx.ArcTo(points[0], new Size(radius, radius), 0, false, SweepDirection.Clockwise, true, false);
}
return geometry;
}
}
}
Just make sure that this class's namespace is loaded into the XAML imports (I use a namespace called "Framework"), and it is simple to use:
<Framework:BalloonDecorator Background="#FFFF6600" PointerLength="50"
CornerRadius="5" Opacity=".9" Margin="200,120,0,0"
HorizontalAlignment="Left" VerticalAlignment="Top" Visibility="{Binding UnitPriceChangedBalloonVisibility}">
<Border CornerRadius="2">
<Border CornerRadius="2">
<Button Height="Auto" Command="{Binding CloseUnitPriceChangedBalloonCommand}" Background="Transparent" BorderBrush="{x:Null}">
<TextBlock Text="Please review the price. The Units have changed."
HorizontalAlignment="Left"
VerticalAlignment="Top"
FontStyle="Italic"
TextWrapping="Wrap"
Margin="10"
/>
</Button>
</Border>
</Border>
</Framework:BalloonDecorator>
Obviously, I tie the visibility to a binding, but you could just set it to true and put this inside your Validation.ErrorTemplate.
Hope this helps!
I've been searching for a better solution than the BalloonDecorator, and ran across the http://www.hardcodet.net/projects/wpf-notifyicon project. It is using the WinAPI at the lowest level, which might give you a head start on building your own solution. It appears that at first glance it could potentially solve it, but I haven't had enough time to verify that the BalloonTip can be made to behave as you've described.
Good luck on your project!
Maybe you can host a Windows Forms control in WPF using the WindowsFormsHost type.
There is a walkthrough available on MSDN on how to do this:
Hosting a Windows Forms Composite Control in WPF
Using this technique you could perhaps use the System.Windows.Forms.ToolTip control. If you set this control's IsBalloon property to true it will display as a balloon window.

C# WPF use polygon to clip control

I'm trying to make a custom ContentControl that takes on the shape of a Polyogon with rounded corners. For some reason when I set the Clip property on the Control, nothing shows up. Any help is appreciated...
PageHost.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Controls;
namespace DtcInvoicer.Controls
{
public class PageHost:UserControl
{
#region public ImageSource Icon;
public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(ImageSource), typeof(PageHost));
public ImageSource Icon
{
get { return GetValue(IconProperty) as ImageSource; }
set { SetValue(IconProperty, value); }
}
#endregion
#region public string Title;
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(PageHost));
public string Title
{
get { return GetValue(TitleProperty).ToString(); }
set { SetValue(TitleProperty, value); }
}
#endregion
#region public double Radius;
public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof(double), typeof(PageHost));
public double Radius
{
get { return (double)GetValue(RadiusProperty); }
set
{
SetValue(RadiusProperty, value);
DoClip();
}
}
#endregion
public PageHost()
{
Loaded += new RoutedEventHandler(PageHost_Loaded);
SizeChanged += new SizeChangedEventHandler(PageHost_SizeChanged);
}
#region Event Handlers
private void PageHost_Loaded(object sender, RoutedEventArgs e)
{
DoClip();
}
private void PageHost_SizeChanged(object sender, SizeChangedEventArgs e)
{
DoClip();
}
#endregion
private void DoClip()
{
Polygon p = new Polygon()
{
Points = new PointCollection()
{
new Point(0, 0),
new Point(ActualWidth - 30, 0),
new Point(ActualWidth, 30),
new Point(ActualWidth, ActualHeight),
new Point(0, ActualHeight)
}
};
Geometry g1 = new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight), Radius, Radius);
Geometry g2 = p.RenderedGeometry;
// Clip = g1; this works, the control shows up with the rounded corners
// Clip = g2; this does not work, nothing shows up
// this is what I want to do, I want to combine the two geometries
// but this does not work either
Clip = new CombinedGeometry(GeometryCombineMode.Intersect, g1, g2);
}
}
}
HomePage.xaml
<control:PageHost x:Class="DtcInvoicer.Pages.HomePage"
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"
xmlns:control="clr-namespace:DtcInvoicer.Controls"
Width="500" Height="250" Radius="20" Background="Aqua">
</control:PageHost>
Setting the clip to a RenderedGeometry fails in this case because the RenderedGeometry has not yet been actually rendered, and is thus not available. For regular geometries, use this in DoClip:
Dispatcher.BeginInvoke(DispatcherPriority.Background, new ThreadStart(delegate
{
Clip = new CombinedGeometry(GeometryCombineMode.Intersect, g1, g2);
}));
With your RenderedGeometry, you would need to add it to the visual tree somewhere and then use its Loaded event before you set the Clip region, which would be hard. Try using a regular Geometry instead of a RenderedGeometry, with the same points, such as A path geometry. Here's an example of where I draw a triangle using a PathGeometry:
double leftPoint = cellRect.Right - 12;
if (leftPoint < cellRect.Left)
leftPoint = cellRect.Left;
double topPoint = cellRect.Top + (cellRect.Height - 4.0) / 2;
if (topPoint < cellRect.Top)
topPoint = cellRect.Top;
double rightPoint = leftPoint + 7;
if (rightPoint > cellRect.Right)
rightPoint = cellRect.Right;
double bottomPoint = topPoint + 4;
if (bottomPoint > cellRect.Bottom)
bottomPoint = cellRect.Bottom;
double middlePoint = leftPoint + 3;
if (middlePoint > cellRect.Right)
middlePoint = cellRect.Right;
PathFigure figure = new PathFigure();
figure.StartPoint = new Point(middlePoint, bottomPoint);
PathFigureCollection figCollection = new PathFigureCollection();
figCollection.Add(figure);
PathSegmentCollection segCollection = new PathSegmentCollection();
LineSegment topSeg = new LineSegment();
topSeg.Point = new Point(rightPoint, topPoint);
segCollection.Add(topSeg);
LineSegment midRightSeg = new LineSegment();
midRightSeg.Point = new Point(leftPoint, topPoint);
segCollection.Add(midRightSeg);
LineSegment midLeftSeg = new LineSegment();
midLeftSeg.Point = new Point(middlePoint+1, bottomPoint);
segCollection.Add(midLeftSeg);
figure.Segments = segCollection;
PathGeometry geo = new PathGeometry();
geo.Figures = figCollection;

Categories

Resources