Simple WPF UI databinding - wpf

I'm trying to write a simple WPF app that has two ellipses, joined by a line, like you might see in a network graph. When the ellipses are animated, I just want the joining line to automagically 'stick' to the canvas locations of the two ellipses that the line joins. The XAML is just a canvas:
<Window x:Class="UIDataBindingDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="400" Width="400">
<Grid>
<Canvas x:Name="cnvExample" />
</Grid>
...and I'm just doing some really simple stuff in the constructor here:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace UIDataBindingDemo
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
// create 2 ellipses, one next to the other, and add them to the canvas
Ellipse el1 = new Ellipse();
Canvas.SetTop(el1, 100);
Canvas.SetLeft(el1, 100);
el1.Width = 20;
el1.Height = 20;
el1.Fill = Brushes.Red;
el1.Stroke = Brushes.Black;
Ellipse el2 = new Ellipse();
Canvas.SetTop(el2, 100);
Canvas.SetLeft(el2, 200);
el2.Width = 20;
el2.Height = 20;
el2.Fill = Brushes.Blue;
el2.Stroke = Brushes.Black;
cnvExample.Children.Add(el1);
cnvExample.Children.Add(el2);
// create a line that connects the 2 ellipses. Bind the two points that define this line to the
// locations of our ellipses, so the line always connects them, through animations, drag and drop
// operations, whatever.
Line line = new Line();
line.StrokeThickness = 3;
line.Stroke = Brushes.Black;
line.SetBinding(Line.X1Property, new Binding("(Canvas.Left)") { Source = el1 });
line.SetBinding(Line.X1Property, new Binding("(Canvas.Top)") { Source = el1 });
line.SetBinding(Line.X1Property, new Binding("(Canvas.Left)") { Source = el2 });
line.SetBinding(Line.X1Property, new Binding("(Canvas.Top)") { Source = el2 });
cnvExample.Children.Add(line);
// animate the second ellipse, so it moves down and to the right, nice and slow
var moveTheBlueOne = new DoubleAnimation(300, TimeSpan.FromSeconds(10));
el2.BeginAnimation(Canvas.LeftProperty, moveTheBlueOne);
el2.BeginAnimation(Canvas.TopProperty, moveTheBlueOne);
}
}
I'm pretty new to WPF, and I'm sure I'm missing something simple. Why am I not seeing the line?

I don't know if it's a cut and paste error but youre assigning each binding to the same DependencyProperty "Line.X1Property", you should use all four X and Y properties to define a starting point and an ending one for a line.
line.SetBinding(Line.X1Property, new Binding("(Canvas.Left)") { Source = el1 });
line.SetBinding(Line.Y1Property, new Binding("(Canvas.Top)") { Source = el1 });
line.SetBinding(Line.X2Property, new Binding("(Canvas.Left)") { Source = el2 });
line.SetBinding(Line.Y2Property, new Binding("(Canvas.Top)") { Source = el2 });
this way it works for me.

Related

System.Windows.Shapes.Path incorrect behavior

After doing some research on subject I didn't find anything, so I'm sorry if the same question was already asked.
Task: make a colored track-line on Canvas after cursor, when the left mouse button is pressed (like brush in Paint).
Problem: I think using System.Windows.Shapes.Path is the best approach to doing this task. Code below works fine, except for one thing: if you try to move your cursor then change direction to the opposite (e.g. the value on X-axis increases, then decreases, but the value on Y-axis, stays constant), you will get an unexpected part of Line, corresponding to the previous direction.
I'm sorry for the tangled description of my problem, but I hope you will get it.
To make it easier for you to reproduce it on your machine I'm adding the solution.
Please, point out for my mistake if I did one!
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Boolean Inserting;
private Path path;
private Boolean isFirstPoint;
public MainWindow()
{
InitializeComponent();
LolCanvas.IsHitTestVisible = true;
}
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (Inserting)
{
Point p = Mouse.GetPosition(LolCanvas);
if (isFirstPoint)
{
PathFigure myPathFigure = new PathFigure();
myPathFigure.StartPoint = new Point(p.X + 5, p.Y + 5);
myPathFigure.Segments = new PathSegmentCollection();
(path.Data as PathGeometry).Figures.Add(myPathFigure);
isFirstPoint = false;
}
else
{
LineSegment myLineSegment = new LineSegment();
myLineSegment.Point = new Point(p.X + 5, p.Y + 5);
(path.Data as PathGeometry).Figures[0].Segments.Add(myLineSegment);
}
}
}
private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
Inserting = true;
path = new Path();
path.Stroke = new SolidColorBrush(Colors.Red);
path.StrokeThickness = 50;
path.Data = new PathGeometry();
(path.Data as PathGeometry).Figures = new PathFigureCollection();
LolCanvas.Children.Add(path);
isFirstPoint = true;
}
private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
Inserting = false;
}
}
}
Xaml:
<Window x:Class="WpfApplication3.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">
<Canvas x:Name="LolCanvas" MouseMove="Canvas_MouseMove" MouseDown="Canvas_MouseDown" MouseUp="Canvas_MouseUp" Background="Black">
</Canvas>
</Window>
Link to the application: http://ge.tt/99aSgyo/v/0?c
Apparently this kind of behavior is correct for path. The problem appeared because of the angle between line parts. It was 180 degrees, so window couldn't render Path propertly in this place.
I had two ways to defeat it:
1) Set IsSmoothJoin property to true for each line segment.
2) Make another Path object, when this kind of problem might occur

Setting ToolStripDropDown.DropShadowEnabled to false on multi level dropdowns

I want to disable the dropdown shadow on the dropdown of a ToolStripDropDownButton. If the dropdown menu contains items that have dropdowns themselves (e.g. multi-level menu) then setting the DropShadowEnabled to false on the ToolStripDropDownButton causes the top level dropdown to appear at the wrong position. See attached picture.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
toolStripDropDownButton1.DropDown.DropShadowEnabled = false;
}
}
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
this.toolStrip1 = new System.Windows.Forms.ToolStrip();
this.toolStripDropDownButton1 = new System.Windows.Forms.ToolStripDropDownButton();
this.item1ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.subitem1ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStrip1.SuspendLayout();
this.SuspendLayout();
//
// toolStrip1
//
this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.toolStripDropDownButton1});
this.toolStrip1.Location = new System.Drawing.Point(0, 0);
this.toolStrip1.Name = "toolStrip1";
this.toolStrip1.Size = new System.Drawing.Size(292, 25);
this.toolStrip1.TabIndex = 0;
this.toolStrip1.Text = "toolStrip1";
//
// toolStripDropDownButton1
//
this.toolStripDropDownButton1.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
this.toolStripDropDownButton1.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.item1ToolStripMenuItem});
this.toolStripDropDownButton1.Image = ((System.Drawing.Image)(resources.GetObject("toolStripDropDownButton1.Image")));
this.toolStripDropDownButton1.ImageTransparentColor = System.Drawing.Color.Magenta;
this.toolStripDropDownButton1.Name = "toolStripDropDownButton1";
this.toolStripDropDownButton1.Size = new System.Drawing.Size(29, 22);
this.toolStripDropDownButton1.Text = "toolStripDropDownButton1";
//
// item1ToolStripMenuItem
//
this.item1ToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.subitem1ToolStripMenuItem});
this.item1ToolStripMenuItem.Name = "item1ToolStripMenuItem";
this.item1ToolStripMenuItem.Size = new System.Drawing.Size(152, 22);
this.item1ToolStripMenuItem.Text = "item1";
//
// subitem1ToolStripMenuItem
//
this.subitem1ToolStripMenuItem.Name = "subitem1ToolStripMenuItem";
this.subitem1ToolStripMenuItem.Size = new System.Drawing.Size(152, 22);
this.subitem1ToolStripMenuItem.Text = "subitem1";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.Add(this.toolStrip1);
this.Name = "Form1";
this.Text = "Form1";
this.toolStrip1.ResumeLayout(false);
this.toolStrip1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.ToolStrip toolStrip1;
private System.Windows.Forms.ToolStripDropDownButton toolStripDropDownButton1;
private System.Windows.Forms.ToolStripMenuItem item1ToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem subitem1ToolStripMenuItem;
}
This is very typical lossage in the ToolStripItem classes. Clearly it is a bug, it probably got introduced when they applied a hack to work around a Windows problem. You can still see the internal bug number in the Reference Source:
public bool DropShadowEnabled {
get {
// VSWhidbey 338272 - DropShadows are only supported on TopMost windows
// due to the flakeyness of the way it's implemented in the OS. (Non toplevel
// windows can have parts of the shadow disappear because another window can get
// sandwiched between the SysShadow window and the dropdown.)
return dropShadowEnabled && TopMost && DisplayInformation.IsDropShadowEnabled;
}
set { // etc... }
}
But without a corresponding fix in the setter and the renderer.
The flakeyness they mentioned actually got fixed in Vista, you are still running on XP so you can't see it. Drop shadows are done differently on the Aero desktop and it is a system setting whether or not they are enabled. So using the property is entirely ineffective on Aero.
These ToolStripItem class bugs didn't get fixed after the .NET 2.0 release, about the entire Winforms team moved over to the WPF group. And they are certainly not getting fixed now, no point filing a bug at connect.microsoft.com although you are free to do so.
With the added wrinkle that the property just cannot have an effect anymore on later versions of Windows since it is now a system setting, the only logical thing to do here is to throw in the towel. Don't change the property.

Silverlight 5: Terrible Performance Issues with DropShadowEffect

Environment Info:
Windows 7 64bit SP1
6GB Memory
Intel(R) Core(TM) i5-2400S CPU # 2.50GHz (4 cores)
Silverlight 5
I have very terrible performance issues with DropShadowEffect in Silerlight.
I created a minimized demo for it:
http://www.peterlee.com.cn/public/ShadowEffectTestDemo/ShadowEffectTestTestPage.html
Click the button Zoom to try to zoom the canvas (with a Star shape with DropShadowEffect). You will find that when the Zoom = 64, your CPU and memory are very crazy.
But, if you remove the DropShadowEffect by clicking the Remove Effect button, and then zoom it, everything is okay.
However, if we use a TextBlock with DropShadowEffect, everything is fine. You can try it by clicking the "Use TextBlock` button.
I don't know what Silverlight is dealing with the TextBlock and my customized Star shape for the DropShadowEffect.
Please help me out. Thanks.
UPDATE:
According to the replies:
I also tried it in Release mode (now the sample demo link was built in Release mode);
I also added GPA acceleration suggested by ChrisF:
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
<param name="EnableGPUAcceleration" value="true" />
<param name="source" value="ShadowEffectTest.xap"/>
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="5.0.61118.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=5.0.61118.0" style="text-decoration:none">
<img src="http://go.microsoft.com/fwlink/?LinkId=161376" alt="Get Microsoft Silverlight" style="border-style:none"/>
</a>
</object>
Here is the UPDATED code
MainPage.xmal:
<UserControl x:Class="ShadowEffectTest.MainPage"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button x:Name="btnZoom" Width="75" Height="23" Content="Zoom"
Grid.Column="0" Grid.Row="0"
Click="btnZoom_Click"/>
<Button x:Name="btnRemoveEffect" Width="100" Height="23" Content="Remove Effect"
Grid.Row="0" Grid.Column="1"
Click="btnRemoveEffect_Click"/>
<Button x:Name="btnUseTextBlock" Width="120" Height="23" Content="Use TextBlock"
Grid.Row="1" Grid.Column="0"
Click="btnUseTextBlock_Click" />
<Canvas x:Name="mainCanvas" Width="200" Height="150" Background="LightBlue"
Grid.Column="1" Grid.Row="1" />
</Grid>
</UserControl>
MainPage.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Diagnostics;
using System.Windows.Media.Effects;
namespace ShadowEffectTest
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
// These are the points for a Star shape
List<Point> points = new List<Point>()
{
new Point(0, 36.3634469292486),
new Point(-12.3606797688474, 43.2188191057189),
new Point(-24.7213595376948, 50.0741912821893),
new Point(-37.0820393065422, 56.9295634586596),
new Point(-34.4313595448175, 42.9654855127096),
new Point(-31.7806797830927, 29.0014075667595),
new Point(-29.130000021368, 15.0373296208095),
new Point(-39.4200000080385, 5.31010468057062),
new Point(-49.709999994709, -4.41712025966827),
new Point(-59.9999999813794, -14.1443451999072),
new Point(-46.0011100186002, -15.919247828416),
new Point(-32.002220055821, -17.694150456925),
new Point(-18.0033300930418, -19.4690530854338),
new Point(-12.0022200620278, -32.3361808961241),
new Point(-6.00111003101392, -45.2033087068145),
new Point(0, -58.0704365175048),
new Point(6.00111003101392, -45.2033087068145),
new Point(12.0022200620278, -32.3361808961241),
new Point(18.0033300930418, -19.4690530854338),
new Point(32.002220055821, -17.694150456925),
new Point(46.0011100186002, -15.919247828416),
new Point(59.9999999813794, -14.1443451999072),
new Point(49.709999994709, -4.41712025966827),
new Point(39.4200000080385, 5.31010468057062),
new Point(29.130000021368, 15.0373296208095),
new Point(31.7806797830927, 29.0014075667595),
new Point(34.4313595448175, 42.9654855127096),
new Point(37.0820393065422, 56.9295634586596),
new Point(24.7213595376948, 50.0741912821893),
new Point(12.3606797688474, 43.2188191057189),
new Point(0, 36.3634469292486)
};
uie = RenderBezier(points);
// This the evil for the performance issues (crazy memory and CPU)
uie.Effect = ShadowEffect;
uie.CacheMode = new BitmapCache();
uie.SetValue(Canvas.LeftProperty, 25.0);
uie.SetValue(Canvas.TopProperty, 25.0);
mainCanvas.Children.Add(uie);
}
private UIElement uie = null;
public Path RenderBezier(List<Point> ctrlPoints, List<List<Point>> innersCtrlPoints = null)
{
// Step 0: Merge ctrlPoints lists
List<List<Point>> ctrlPointsLists;
if (innersCtrlPoints == null)
{
ctrlPointsLists = new List<List<Point>>(1);
ctrlPointsLists.Add(ctrlPoints);
}
else
{
ctrlPointsLists = new List<List<Point>>(1 + innersCtrlPoints.Count);
ctrlPointsLists.Add(ctrlPoints);
foreach (List<Point> list in innersCtrlPoints)
ctrlPointsLists.Add(list);
}
PathGeometry pg = new PathGeometry();
foreach (List<Point> list in ctrlPointsLists)
{
// Step 0: check (Debug.Assert)
Debug.Assert(list.Count % 3 == 1,
"public Path RenderBezier(IList<Point> ctrlPoints): number of control points is not 3n+1.");
int n = (list.Count - 1) / 3; // Number of BezierSegments
Debug.Assert(n > 0,
"public Path RenderBezier(IList<Point> ctrlPoints): at least one Bezier segment required.");
// Step 1: Add BezierSegments to PathFigure
PathFigure pf = new PathFigure();
pf.StartPoint = list[0];
for (int i = 0; i < n; ++i)
pf.Segments.Add(GetBezierSegment(
list[3 * i + 1],
list[3 * i + 2],
list[3 * i + 3]
));
// Step 2: Add PathFigures to PathGeometry
pg.Figures.Add(pf);
}
// Step 3: Add PathGemotry to GeometryGroup
GeometryGroup gg = new GeometryGroup();
gg.Children.Add(pg);
// Step 4: Set GeometryGroup as Path.Data
Path path = new Path();
path.Data = gg;
// Step 5: Set some Path properties
// if (ShowOutline)
{
path.Stroke = new SolidColorBrush(Colors.Black);
path.StrokeThickness = 1.0;
path.StrokeEndLineCap = PenLineCap.Round;
path.StrokeLineJoin = PenLineJoin.Round;
path.StrokeStartLineCap = PenLineCap.Round;
}
// Finally, return it
return path;
}
// This the evil for the performance issues (crazy memory and CPU)
private static DropShadowEffect ShadowEffect
{
get
{
return new DropShadowEffect()
{
Color = Colors.Blue,
BlurRadius = 5,
Direction = 0,
ShadowDepth = 0
};
}
}
private static BezierSegment GetBezierSegment(Point p1, Point p2, Point p3)
{
BezierSegment bs = new BezierSegment();
bs.Point1 = p1;
bs.Point2 = p2;
bs.Point3 = p3;
return bs;
}
public static readonly double[] ZoomingSteps = new double[]
{
1.0,
1.5,
2.0,
3.0,
4.0,
6.0,
8.0,
12.0,
16.0,
24.0,
32.0,
48.0,
64.0,
128.0
};
private int index = 0;
private void btnZoom_Click(object sender, RoutedEventArgs e)
{
if (index >= ZoomingSteps.Length - 1)
return;
ScaleTransform st = new ScaleTransform();
st.ScaleX = st.ScaleY = ZoomingSteps[index++];
btnZoom.Content = ZoomingSteps[index].ToString();
mainCanvas.RenderTransform = st;
}
private void btnRemoveEffect_Click(object sender, RoutedEventArgs e)
{
index = 0;
btnZoom.Content = "Zoom";
uie.Effect = null;
mainCanvas.RenderTransform = new ScaleTransform();
}
// If I use TextBlock as the child UIElement, then everything is okay
// path = new TextBlock() { Text = "Text Block" };
private void btnUseTextBlock_Click(object sender, RoutedEventArgs e)
{
mainCanvas.Children.Remove(uie);
index = 0;
btnZoom.Content = "Zoom";
uie = new TextBlock() { Text = "Text Block" };
mainCanvas.Children.Add(uie);
uie.Effect = ShadowEffect;
uie.CacheMode = new BitmapCache();
mainCanvas.RenderTransform = new ScaleTransform();
}
}
}
I've tried running WinDbg against your application and dumpheap the application. here is the Result. Now dont worry, I dont understand half of it either. But I went a little deeper and there seem to be large amounts of arrays/lists where atleast 80% are null.
You might want to look at WinDbg yourself. Here is a small Tutorial
I'll try to look at it in the morning if I can and I hope I've helped you atleast in the right direction
Here's my trick: never use DropShadowEffect. Create a clone of whatever you want to create a drop shadow for, put it behind the main content, and use a BlurEffect on it. Much better performance, much better quality. I have no idea why.
I think the problem is here:
uie.CacheMode = new BitmapCache();
Typically, this tells silverlight to try and cache the bitmap in memory so that the computer does not need to re-render the element in your application.
Look here for the suggested tips on silverlight performance:
http://msdn.microsoft.com/en-us/library/cc189071(v=vs.95).aspx
The problem has to do with how the rendering pipeline of Silverlight works. The DropShadowEffect is just a pixel shader, and pixel shaders work in Silverlight by creating a buffered copy of the image they will be applied to and then allow you to change pixel properties of the real image using the values of the buffered copy. This buffered copy is the entire image and is not affected by clipping. Because zooming creates a very large pre-clipped image, the pixel shader must make a physical buffered copy of a very large pre-clipped image...this is why the performance is so bad when you zoom.
The only solution I have found for this is to disable the pixel shader effect while you are zooming (or panning the zoomed image), and add it back when the image is zoomed in (or panning is complete). This way you don't incur the performance penalty of the pixel shader being applied for every frame of zooming or panning.

Databound Triangle Creation?

I've got a requirement to create several shapes based on a supplied size (all of them have the same height/width) and have their sizes be databound to that supplied property on the datacontext.
Most of the shapes are easy: Circle (ellipse with height/width bound), square (rectangle with height/width bound), diamond (same as square, then use a RotateTransform), + (two lines), X (two lines).
But I'm trying to figure out how to do it for a triangle and I can't figure it out. It needs to be a filled object, so I can't just do it with three lines.
But all of the ways i've seen to do it (w/ a Path or a Polygon) end up taking Point objects (StartPoint, EndPoint, etc). And you can't bind to the X or Y values of the Point object.
Am I missing something? Or do I need to write my own custom shape or something?
Edit: To add a little bit of clarity... the type of triangle I'm creating doesn't really matter. It can be equilateral or isosceles. I was targeting an isosceles, so that it would have a base with the databound width and the top "tip" of the triangle will be at the mid-point of the databound width and at Y=0. That was just an optimization for simplicity's sake
The behavior class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Shapes;
using System.Windows.Media;
namespace WpfApplication1
{
public enum ShapeType
{
Rectangle,
Isosceles,
Ellipse,
Dice,
Hexagon
}
public class PathControl
{
public static readonly DependencyProperty ShapeTypeProperty =
DependencyProperty.RegisterAttached("ShapeType",
typeof(ShapeType?),
typeof(DependencyObject),
new PropertyMetadata(null,
new PropertyChangedCallback((sender, args) =>
{
Path path = sender as Path;
ShapeType? shapeType = (ShapeType?)args.NewValue;
//todo: use a WeakEvent
path.SizeChanged +=
(pathSender, pathArgs) =>
{
PathControl.InvalidatePath((Path)sender, shapeType);
};
})));
private static void InvalidatePath(Path path, ShapeType? shapeType)
{
if (path != null
&& shapeType.HasValue)
{
string source = null;
double netWidth = path.Width - 2 * path.StrokeThickness,
netHeight = path.Height - 2 * path.StrokeThickness;
if (shapeType == ShapeType.Rectangle)
{
source = string.Format("M0,0 h{0} v{1} h-{0} z",
new object[2]
{
netWidth,
netHeight
});
}
else if (shapeType == ShapeType.Isosceles)
{
source = string.Format("M0,{1} l{0},-{1} {0},{1} z",
new object[2]
{
netWidth / 2,
netHeight
});
}
else
{
throw new NotImplementedException(shapeType.ToString());
}
path.Data = Geometry.Parse(source);
}
}
public static void SetShapeType(DependencyObject o, ShapeType e)
{
o.SetValue(PathControl.ShapeTypeProperty, e);
}
public static ShapeType? GetShapeType(DependencyObject o)
{
return (ShapeType)o.GetValue(PathControl.ShapeTypeProperty);
}
}
}
The XAML:
<Window x:Class="WpfApplication1.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"
xmlns:local="clr-namespace:WpfApplication1">
<Grid>
<Path Width="100" Height="100" Stroke="Green" StrokeThickness="2" Fill="Yellow"
local:PathControl.ShapeType="Isosceles">
<Path.RenderTransform>
<RotateTransform Angle="90"></RotateTransform>
</Path.RenderTransform>
</Path>
</Grid>
</Window>
Binding to the Points is the best/only way. The X and X properties of a Point cannot be bound to because they do not raise the PropertyChanged event. The Point is a structure and structures should be read-only.
The PointCollection class raises the correct events so you can bind to it. This allows you to manipulate the triangles by modifying the collection of point by replacing the points. Do not change the point but replace them so the proper events will be raised.

Silverlight crash after button event handler execution

Dear team, this is my problem:
Inside a control I have a generic number (I know it only at runtime) of sub controls.
Each sub-control is a LanguageTextView which contains a rich text editor from Liquid.
A button is used to control how these rich texts have to be shown inside the parent control.
There are 2 options: a single LanguageTextView or all the LanguageTextView items equally spaced inside the parent control.
To solve this problem I used a Grid in the container.
With code behind I populate the grid with rich texts on the "load" event:
foreach (OM_Language lang in LanguageDataManager.INSTANCE.ActiveLanguages) {
LanguageTextView tb = new LanguageTextView();
tb.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
tb.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Stretch;
tb.HorizontalAlignment = HorizontalAlignment.Stretch;
tb.HorizontalContentAlignment = HorizontalAlignment.Stretch;
tb.init(lang);
tb.onTextChangedEvent += new LanguageTextView.onTextChangedEventType(tb_onTextChangedEvent);
if ((_module.Texts != null) && (_module.Texts.ContainsKey(lang.OMID.Value))) {
tb.Text = _module.Texts[lang.OMID.Value];
}
_textBoxList.Add(lang.OMID.Value, tb);
}
_textBoxList is a dictionary which is passed to a grid controller with the grid object to control. Grid controller, called MultiLanguageGridController, handle how LanguageTextView have to be shown inside the grid (a single object or all the objects together).
As first step, when created, grid controller place the LanguageTextView one per column with following code:
int count = 0;
foreach (int key in _uiElements.Keys) {
FrameworkElement fe = _uiElements[key];
ColumnDefinition cd = new ColumnDefinition();
cd.Width = new GridLength(1, GridUnitType.Star);
_cds.Add(key, cd);
_grid.ColumnDefinitions.Add(cd);
_grid.Children.Add(fe);
Grid.SetColumn(fe, count);
Grid.SetRow(fe, 0);
count++;
}
To control layout there are two methods: showAllObjects and showSingleObjet(objectid).
showAllObject is like this:
foreach (int key in _uiElements.Keys) {
_cds[key].Width = new GridLength(1, GridUnitType.Star);
}
showSingleObject is as follow:
foreach(int key in _uiElements.Keys){
if(key == objectId){
_cds[key].Width = new GridLength(1, GridUnitType.Star);
}else{
_cds[key].Width = new GridLength(0);
}
}
When control button is pressed, the button event is handled this way:
ModuleListItemData mld = this.DataContext as ModuleListItemData;
if (mld == null) {
return;
}
mld.IsShowAllLanguages = !mld.IsShowAllLanguages;
IsShowAllLanguages is a property which fires a property changed event.
Property changed event is intercepted and handled this way:
if ("IsShowAllLanguages".Equals(e.PropertyName)) {
if (tmd.IsShowSingleLanguage) {
_gridCtrl.showSingleObject(tmd.SelectedLanguage.OMID.Value);
} else {
_gridCtrl.showAllObjects();
}
return;
}
Where _gridCtrl is a reference to MultiLanguageGridController.
After second round-trip on button event handling, application crashes.
Debugging the application, I setup a break point at the last line of button clich event handler as follows:
private void _btnShowAllLang_Click(object sender, RoutedEventArgs e) {
ModuleListItemData mld = this.DataContext as ModuleListItemData;
if (mld == null) {
return;
}
====> mld.IsShowAllLanguages = !mld.IsShowAllLanguages; <=== Break point here
}
Each time the control button is pressed, break point is reached, but right after exiting from method pressing F10 (visual studio 2010), application stops and after I while I get an exception:
An unhandled exception of type 'System.AccessViolationException' occurred in System.Windows.dll. Trying to read protected memory.
I'm not able to proceed with debugging outside the event handler method!
Any ideas??
Thanks in advance for any support.
Strange but true, the solution was to change XAML inclusion from:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controlsToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
xmlns:myConverters="clr-namespace:ArgoWeb.Utility.Data"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignWidth="608" d:DesignHeight="112"
to:
xmlns:my="clr-namespace:ArgoWeb.UI.UserControls.ModuleView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:ArgoWeb.Utility.Data"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignWidth="482" d:DesignHeight="144" Height="100"
This solved the problem.

Resources