Custom ComboBox, ComboItem - c#

I need to create a custom combo box that display shapes. I created the shapes by extending the Shape class, and implementing the DefiningGeometry function like this:
public abstract class MyShape : Shape
{
public static readonly DependencyProperty SizeProperty = DependencyProperty.Register("Size", typeof(Double), typeof(MapShape));
public static readonly DependencyProperty RotationAngleProperty = DependencyProperty.Register("RotationAngle", typeof(Double), typeof(MapShape), new PropertyMetadata(0.0d));
public double Size
{
get { return (double)this.GetValue(SizeProperty); }
set { this.SetValue(SizeProperty, value); }
}
public double RotationAngle
{
get { return (double)this.GetValue(RotationAngleProperty); }
set { this.SetValue(RotationAngleProperty, value); }
}
protected override Geometry DefiningGeometry
{
get
{ return null; }
}
}
I can extend that class and create any other shape I want. for instance, I have one that looks like an arrow:
public class Arrow : MyShape
{
public Arrow() {
}
protected override Geometry DefiningGeometry
{
get
{
double oneThird = this.Size / 3;
double twoThirds = (this.Size * 2) / 3;
double oneHalf = this.Size / 2;
Point p1 = new Point(0.0d, oneThird);
Point p2 = new Point(0.0d, twoThirds);
Point p3 = new Point(oneHalf, twoThirds);
Point p4 = new Point(oneHalf, this.Size);
Point p5 = new Point(this.Size, oneHalf);
Point p6 = new Point(oneHalf, 0);
Point p7 = new Point(oneHalf, oneThird);
List<PathSegment> segments = new List<PathSegment>(3);
segments.Add(new LineSegment(p1, true));
segments.Add(new LineSegment(p2, true));
segments.Add(new LineSegment(p3, true));
segments.Add(new LineSegment(p4, true));
segments.Add(new LineSegment(p5, true));
segments.Add(new LineSegment(p6, true));
segments.Add(new LineSegment(p7, true));
List<PathFigure> figures = new List<PathFigure>(1);
PathFigure pf = new PathFigure(p1, segments, true);
figures.Add(pf);
RotateTransform rt = new RotateTransform(this.RotationAngle);
Geometry g = new PathGeometry(figures, FillRule.EvenOdd, rt);
return g;
}
}
}
I can add this shapes on XAML or code and they work just fine.
Now, those shapes are displayed on a graphics object somewhere in my form which is no relevant. The requirement I have is for the shapes on the graphic object in the form to be changed by the client from a ComboBox. So, basically I need to display the shapes inside the combo box as well. I really don't need to use these classes I am showing here, it is just for clarification I add them to this note. But I do need to customize the combobox to show shapes in the items. One way I thought is using the ControlTemplate, Any other ideas, code, readings?
Thanks!

If I understood, what you want can be achieved by customizing the ItemTemplate property of the ComboBox.
<ComboBox ...>
<ComboBox.ItemTemplate>
<DataTemplate>
<!-- Whatever UI -->
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Related

How can I calculate Actual Size of TextEffect?

I made a customcontrol inherit from TextBlock for LetterSpacing, here are my codes:
public class NewTextBlock : TextBlock
{
public NewTextBlock() : base() {
var dp = DependencyPropertyDescriptor.FromProperty(
TextBlock.TextProperty,
typeof(TextBlock));
dp.AddValueChanged(this, (sender, args) =>
{
LetterSpacingChanged(this);
});
}
static void LetterSpacingChanged(DependencyObject d)
{
NewTextBlock TB = d as NewTextBlock;
TB.TextEffects.Clear();
for (int i = 1; i <= TB.Text.Length; i++)
{
TranslateTransform transform = new TranslateTransform(TB.LetterSpacing, 0);
TextEffect effect = new TextEffect();
effect.Transform = transform;
effect.PositionStart = i;
effect.PositionCount = TB.Text.Length;
TB.TextEffects.Add(effect);
if (effect.CanFreeze)
{
effect.Freeze();
}
}
}
public double LetterSpacing
{
get { return (double)GetValue(LetterSpacingProperty); }
set { SetValue(LetterSpacingProperty, value); }
}
// Using a DependencyProperty as the backing store for LetterSpacing. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LetterSpacingProperty =
DependencyProperty.Register("LetterSpacing", typeof(double), typeof(NewTextBlock), new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(LetterSpacingChanged)));
private static void LetterSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LetterSpacingChanged(d);
}
}
Now when I use it like this:
<Test:NewTextBlock Background="Red" HorizontalAlignment="Center" VerticalAlignment="Center" LetterSpacing="5" Text="123123123123">
<Test:NewTextBlock.LayoutTransform>
<RotateTransform Angle="45"></RotateTransform>
</Test:NewTextBlock.LayoutTransform>
</Test:NewTextBlock>
It turns out to be like this:
enter image description here
As the red background showed, it looks like the actual width of the NewTextBlock does not include the width of the TextEffect.
I want to find a way to get the actual width of the TextEffect and set a right actual width to the NewTextBlock.
How can I do this?
Don't use the DependencyPropertyDescriptor to register a property changed callback. If you are not careful (which is the case with your code) it will create a (potentially severe) per instance memory leak.
Better override the the dependency property's metadata in a static constructor to register a new callback (see example below).
TextBlock does not support custom character spacing. TextBlock completely relies on the provided font (or typeface) to calculate the actual text width. Since MeasureOverride is sealed, you must force the new Width after a change of the Text value or any text related property change in general (for example FontSize etc.).
This means, to implement the sizing behavior correctly you would have to override the property metadata for all relevant properties to trigger the recalculations of the actual NewTextBlock width. You can follow the metadata override for the TextBlock.TextProperty property of the below example.
To calculate the width of a formatted string you usually use the FormattedText class (see below). In your case, you would have to include the current character spacing into the computation.
public class NewTextBlock : TextBlock
{
public double LetterSpacing
{
get => (double)GetValue(LetterSpacingProperty);
set => SetValue(LetterSpacingProperty, value);
}
public static readonly DependencyProperty LetterSpacingProperty = DependencyProperty.Register(
"LetterSpacing",
typeof(double),
typeof(NewTextBlock),
new FrameworkPropertyMetadata(default(double), OnLetterSpacingChanged));
static NewTextBlock()
{
TextProperty.OverrideMetadata(
typeof(NewTextBlock),
new FrameworkPropertyMetadata(default(string), OnTextChanged));
}
public NewTextBlock()
{
this.TextEffects = new TextEffectCollection();
}
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
=> (d as NewTextBlock).ApplyTextEffects();
private static void OnLetterSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
=> (d as NewTextBlock).ApplyTextEffects();
private void ApplyTextEffects()
{
if (string.IsNullOrWhiteSpace(this.Text))
{
return;
}
this.TextEffects.Clear();
for (int characterIndex = 1; characterIndex <= this.Text.Length; characterIndex++)
{
var transform = new TranslateTransform(this.LetterSpacing, 0);
var effect = new TextEffect
{
Transform = transform,
PositionStart = characterIndex,
PositionCount = this.Text.Length
};
this.TextEffects.Add(effect);
if (effect.CanFreeze)
{
effect.Freeze();
}
}
AdjustCurrentWidth();
}
private void AdjustCurrentWidth()
{
var typeface = new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, this.FontStretch);
double pixelsPerDip = VisualTreeHelper.GetDpi(this).PixelsPerDip;
var formattedText = new FormattedText(this.Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, this.FontSize, this.Foreground, pixelsPerDip);
double totalLetterSpacingWidth = (this.Text.Length - 1) * this.LetterSpacing;
double totalTextWidth = formattedText.WidthIncludingTrailingWhitespace + totalLetterSpacingWidth;
this.Width = totalTextWidth;
}
}
Maybe I missed the point in this STextBlock but it looks to me like you could (maybe should) just use the Glyphs frameworkelement instead.
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/introduction-to-the-glyphrun-object-and-glyphs-element?view=netframeworkdesktop-4.8
https://learn.microsoft.com/en-us/dotnet/api/system.windows.documents.glyphs?view=windowsdesktop-7.0
EG
<!-- "Hello World!" with explicit character widths for proportional font -->
<Glyphs
FontUri = "C:\WINDOWS\Fonts\ARIAL.TTF"
FontRenderingEmSize = "36"
UnicodeString = "Hello World!"
Indices = ",80;,80;,80;,80;,80;,80;,80;,80;,80;,80;,80"
Fill = "Maroon"
OriginX = "50"
OriginY = "225"
/>
<!-- "Hello World!" with fixed-width font -->
<Glyphs
FontUri = "C:\WINDOWS\Fonts\COUR.TTF"
FontRenderingEmSize = "36"
StyleSimulations = "BoldSimulation"
UnicodeString = "Hello World!"
Fill = "Maroon"
OriginX = "50"
OriginY = "300"
/>
I found another solution from the code of #BionicCode
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
if (!string.IsNullOrEmpty(Text))
{
double PositionX = Padding.Left;
double PositionY = Padding.Top;
foreach (var i in Text)
{
var FT = new FormattedText(i.ToString(), CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, new Typeface(FontFamily,FontStyle,FontWeight,FontStretch), FontSize, Foreground);
drawingContext.DrawText(FT, new Point(PositionX, PositionY));
PositionX += FT.Width+LetterSpacing;
var t = Width;
}
}
}

Create Skiasharp path around element like a border in Xamarin forms

I have a control in my Xamarin Forms application. Now I wish to build a custom progress bar around it, like a border countdown. But to start things off I need to know how to position this properly around my element (a viewrenderer) like a full border. I can successfully create a border object but the placing of the object is not right.
What I do is that I gather the X, Y, Width and Height from my element but when I add a breakpoint to this, it does not seem to give me any useful and accurate numbers. And it is shown when I deploy, the skiasharp object does not align as it should.
This is what I’m working with:
public class SvgPathCountdown : SKCanvasView
{
public SvgPathCountdown()
{
this.InvalidateSurface();
}
private static void OnPropertyChanged(BindableObject bindable, object oldVal, object newVal)
{
var svgPath = bindable as SvgPathCountdown;
svgPath?.InvalidateSurface();
}
public static readonly BindableProperty XValueProperty =
BindableProperty.Create(nameof(XValue), typeof(double), typeof(SvgPathCountdown), 10.0, propertyChanged: OnPropertyChanged);
public double XValue
{
get { return (double)GetValue(XValueProperty); }
set { SetValue(XValueProperty, value); }
}
public static readonly BindableProperty YValueProperty =
BindableProperty.Create(nameof(YValue), typeof(double), typeof(SvgPathCountdown), 10.0, propertyChanged: OnPropertyChanged);
public double YValue
{
get { return (double)GetValue(YValueProperty); }
set { SetValue(YValueProperty, value); }
}
public static readonly BindableProperty WidthValueProperty =
BindableProperty.Create(nameof(WidthValue), typeof(double), typeof(SvgPathCountdown), 10.0, propertyChanged: OnPropertyChanged);
public double WidthValue
{
get { return (double)GetValue(WidthValueProperty); }
set { SetValue(WidthValueProperty, value); }
}
public static readonly BindableProperty HeightValueProperty =
BindableProperty.Create(nameof(HeightValue), typeof(double), typeof(SvgPathCountdown), 10.0, propertyChanged: OnPropertyChanged);
public double HeightValue
{
get { return (double)GetValue(HeightValueProperty); }
set { SetValue(HeightValueProperty, value); }
}
protected override void OnPaintSurface(SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
SKRect bounds;
var rect = SKRect.Create((float)XValue, (float)YValue, (float)WidthValue, (float)HeightValue);
var paint = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.Blue,
StrokeWidth = 10,
StrokeCap = SKStrokeCap.Round,
StrokeJoin = SKStrokeJoin.Round
};
// draw fill
canvas.DrawRect(rect, paint);
// change the brush (stroke with red)
paint.Style = SKPaintStyle.Stroke;
paint.Color = SKColors.Red;
// draw stroke
canvas.DrawRect(rect, paint);
}
And XAML:
<controls:CustomElement x:Name = "myControlElement" AbsoluteLayout.LayoutBounds="0.5, 0.2, 0.85, 0.2" AbsoluteLayout.LayoutFlags="All" />
<controls:SvgPathCountdown x:Name = "svgPath" />
Code behind here i set the values:
svgPath.XValue = myControlElement.X;
svgPath.YValue = myControlElement.Y;
svgPath.WidthValue = myControlElement.AnchorX;
svgPath.HeightValue = myControlElement.HeightRequest;
How do i adjust my code so it matches the element when i do SKRect.Create with the set values in code behind? Maybe somehow i can get the svg path of a view control?
A simple and straightforward solution would be to put the Skia element and your button into the same grid layout. Then give the other element a reasonable sized margin and draw your path in a way, that it covers the free space you get after the view has layouted:
<Grid AbsoluteLayout.LayoutBounds="0.5, 0.2, 0.85, 0.2" AbsoluteLayout.LayoutFlags="All">
<controls:SvgPathCountdown x:Name = "svgPath" />
<controls:CustomElement x:Name = "myControlElement" Margin="5,5,5,5" />
</Grid>
Also make sure that you place your element AFTER the Skia Path element in order to prevent it from being overlayed by the skia element so that it remains clickable (if you use e.g. a button).
When drawing your path you should already have the width and height of the skia canvas in e.Info

Window instances - how to get my main window canvas inside other class?

I have class, that creates Shapes for me (I tried to create some kind of "class factory" but im not sure if this is correct term for that I have created.
Problem is described in comments in my code.
public static Ellipse SomeCircle()
{
Ellipse e = new Ellipse();
double size = 10;
e.Height = size;
e.Width = size;
e.Fill = new SolidColorBrush(Colors.Orange);
e.Fill.Opacity = 0.8;
e.Stroke = new SolidColorBrush(Colors.Black);
// i want to have something like this here:
// canvas1.Children.Add(e);
// but I cant access non-static canvas1 from here
// I need this to place my ellipse in desired place
// (line below will not work if my Ellipse is not placed on canvas
// e.Margin = new Thickness(p.X - e.Width * 2, p.Y - e.Height * 2, 0, 0);
return e;
}
I have no idea how to workaround this.
I don't want to pass that canvas by parameter in my whole application...
Since you do not want to pass your Canvas around as a parameter, you could try creating an Extension Method which would act on your Canvas Object.
namespace CustomExtensions
{
public static class Shapes
{
public static Ellipse SomeCircle(this Canvas dest)
{
Ellipse e = new Ellipse();
double size = 10;
e.Height = size;
e.Width = size;
e.Fill = new SolidColorBrush(Colors.Orange);
e.Fill.Opacity = 0.8;
e.Stroke = new SolidColorBrush(Colors.Black);
dest.Children.Add(e);
return e;
}
}
}
Usage remember to add the CustomExtensions Namespace to your usings.
canvas1.SomeCircle();

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