I would like to create a GUI with Visual Studio which is composed mainly by a Treeview and a canvas. The functionality of the application is initially to create somewhat complicated shapes on the canvas which should be connected later in order to construct a compact unit (the final purpose is not graphical, but represent functions and procedures). More particularly, the user would have the possibility by a shape selection on the Treeview to click on the canvas and the respective shape to be drawn. He has also the possibility to move the shapes on the canvas and to connect them with lines. It becomes apparent that the application should make extended use of mouselisteners (mouseEvents).
Is a wpf the appropriate type of project to accomplish something like that?
Given that they shapes are not plain but they contain contents, other shapes, buttons and mouseEvents, the code demanded for their creation is not confined. Should it be entirely in the MainWindow.xaml.cs or it would be better directed to split the implementation to more classes (e.g. one separate class for each shape)? For example the code for the Rectangle is so far:
Double rectWidth = 100;
Double rectHeight = rectWidth;
shapeToRender = new Rectangle() { Fill = Brushes.Red, Height = 100, Width = 100, RadiusX = 7, RadiusY = 7 };
shapeToRender.Stroke = Brushes.Black;
shapeToRender.StrokeThickness = 3;
currentShape = SelectedShape.Empty;
Canvas.SetLeft(shapeToRender, e.GetPosition(canvasDrawingArea).X - rectWidth / 2);
Canvas.SetTop(shapeToRender, e.GetPosition(canvasDrawingArea).Y - rectHeight / 2);
canvasDrawingArea.Children.Add(shapeToRender);
double xCircle1 = e.GetPosition(canvasDrawingArea).X + (rectWidth)/2;
double yCircle1 = e.GetPosition(canvasDrawingArea).Y + (rectHeight)/4;
double xCircle2 = xCircle1;
double yCircle2 = e.GetPosition(canvasDrawingArea).Y - (rectWidth) / 4;
double xCircle3 = e.GetPosition(canvasDrawingArea).X - rectWidth / 2;
double yCircle3 = e.GetPosition(canvasDrawingArea).Y;
Ellipse s1Ellipse = new Ellipse() { Fill = Brushes.Yellow, Height = 10, Width = 10 };
Canvas.SetLeft(s1Ellipse, xCircle1-5);
Canvas.SetTop(s1Ellipse, yCircle1-5);
canvasDrawingArea.Children.Add(s1Ellipse);
Ellipse s2Ellipse = new Ellipse() { Fill = Brushes.Yellow, Height = 10, Width = 10 };
Canvas.SetLeft(s2Ellipse, xCircle2-5);
Canvas.SetTop(s2Ellipse, yCircle2-5);
canvasDrawingArea.Children.Add(s2Ellipse);
Ellipse s3Ellipse = new Ellipse() { Fill = Brushes.Yellow, Height = 10, Width = 10 };
Canvas.SetLeft(s3Ellipse, xCircle3 - 5);
Canvas.SetTop(s3Ellipse, yCircle3 - 5);
canvasDrawingArea.Children.Add(s3Ellipse);
Is it reasonable to build a separate class that is responsible to create the rectangles? How could then I manipulate elements of the MainWindow and the mousEvents inside the new class?
From what you've wrote WPF is exactly what you need. IMHO you should create class (custom control) to represent your diagram items. You don't necessary need to write different class for every shape on diagram. If the look is all that is different you can always use different templates to change the representation of your diagram control.
How to build such a thing is rather complex question. I've came across a very useful article on creating diagram designer in WPF. It is actually a set of articles. They would be a good place to start. Here's the link for the last article (because it contains links to previous articles).
Related
I have a UWP app, which I should start by pointing out that it uses very little XAML. The views are built from JSON object recieved from an API. This means that the vast majority of everything is done in C#, and therefore adds a little complexity to my problem.
I basically want to have a panel (e.g. Grid) that can have rounded corners and have a drop shadow applied to it. The drop shadow should also have the rounded corners, this can be seen in the sample below.
I have looked at the DropShadowPanel as part of the Windows Community Toolkit, but this from what I can tell doesn't do the rounded corners unless I change the content to be a rectangle or some other shape.
To use this as a solution would mean the XAML equivalent of something like:
<Grid>
<toolkit:DropShadowPanel>
<Rectangle />
<toolkit:DropShadowPanel>
<Grid CornerRadius="30">
<!-- My Content -->
</Grid>
</Grid>
To me, this seems like an inefficient use of XAML!
I have also discovered the Composition Pro Toolkit, which to me looks bery interesting as it is all code behind. In particular the ImageFrame control looks to achieve the basis of what I require - although far more advanced than my needs.
The below has been based on the ImageFrame, but doesn't work (content is my grid):
protected FrameworkElement AddDropShadow(FrameworkElement content)
{
var container = new Grid { HorizontalAlignment = content.HorizontalAlignment, VerticalAlignment = content.VerticalAlignment, Width = content.Width, Height = content.Height };
var canvas = new Canvas { HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch };
content.Loaded += (s, e) =>
{
var compositor = ElementCompositionPreview.GetElementVisual(canvas).Compositor;
var root = compositor.CreateContainerVisual();
ElementCompositionPreview.SetElementChildVisual(canvas, root);
var shadowLayer = compositor.CreateSpriteVisual();
var frameLayer = compositor.CreateLayerVisual();
var frameContent = compositor.CreateShapeVisual();
root.Children.InsertAtBottom(shadowLayer);
root.Children.InsertAtTop(frameLayer);
frameLayer.Children.InsertAtTop(frameContent);
var rectangle = root.Compositor.CreateRoundedRectangleGeometry();
rectangle.Size = new Vector2((float)content.ActualWidth, (float)content.ActualHeight);
rectangle.CornerRadius = new Vector2(30f);
var shape = root.Compositor.CreateSpriteShape(rectangle);
shape.FillBrush = root.Compositor.CreateColorBrush(Colors.Blue);
//var visual = root.Compositor.CreateShapeVisual();
frameContent.Size = rectangle.Size;
frameContent.Shapes.Add(shape);
//create mask layer
var layerEffect = new CompositeEffect
{
Mode = Microsoft.Graphics.Canvas.CanvasComposite.DestinationIn,
Sources = { new CompositionEffectSourceParameter("source"), new CompositionEffectSourceParameter("mask") }
};
var layerEffectFactory = compositor.CreateEffectFactory(layerEffect);
var layerEffectBrush = layerEffectFactory.CreateBrush();
//CompositionDrawingSurface
var graphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(compositor, new Microsoft.Graphics.Canvas.CanvasDevice(forceSoftwareRenderer: false));
var frameLayerMask = graphicsDevice.CreateDrawingSurface(new Size(0, 0), Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, Windows.Graphics.DirectX.DirectXAlphaMode.Premultiplied);
layerEffectBrush.SetSourceParameter("mask", compositor.CreateSurfaceBrush(frameLayerMask));
frameLayer.Effect = layerEffectBrush;
var shadow = root.Compositor.CreateDropShadow();
//shadow.SourcePolicy = CompositionDropShadowSourcePolicy.InheritFromVisualContent;
shadow.Mask = layerEffectBrush.GetSourceParameter("mask");
shadow.Color = Colors.Black;
shadow.BlurRadius = 25f;
shadow.Opacity = 0.75f;
shadow.Offset = new Vector3(0, 0, 0);
shadowLayer.Shadow = shadow;
content.Opacity = 0; //hiding my actual content to see the results of this
};
container.Children.Add(canvas);
container.Children.Add(content);
return container;
}
In these tests, I am doing the same inefficient use of object, creating another container that has both the composition canvas, and also the grid. If possible, I'd like to apply the composition directly to the original content grid.
I am completely new to composition, so any thoughts, pointers, glaring errors or solutions would be most welcomed.
A Hack Solution?
I have changed my method to the following, visually it works - but is it right?
protected FrameworkElement AddDropShadow(FrameworkElement content)
{
var container = new Grid { HorizontalAlignment = content.HorizontalAlignment, VerticalAlignment = content.VerticalAlignment };
var rectangle = new Rectangle { Fill = new SolidColorBrush(Colors.Transparent) };
content.Loaded += (s, e) =>
{
rectangle.Fill = new SolidColorBrush(Colors.Black);
rectangle.Width = content.ActualWidth;
rectangle.Height = content.ActualHeight;
rectangle.RadiusX = 30;
rectangle.RadiusY = 30;
var compositor = ElementCompositionPreview.GetElementVisual(rectangle).Compositor;
var visual = compositor.CreateSpriteVisual();
visual.Size = new Vector2((float)content.ActualWidth, (float)content.ActualHeight);
var shadow = compositor.CreateDropShadow();
shadow.BlurRadius = 30f;
shadow.Mask = rectangle.GetAlphaMask();
shadow.Opacity = 0.75f;
visual.Shadow = shadow;
ElementCompositionPreview.SetElementChildVisual(rectangle, visual);
};
container.Children.Add(rectangle);
container.Children.Add(content);
return container;
}
The concept here is that my container grid holds a rectangle and my content grid (or other element).
The first error of this method is that is assumes my input FrameworkElement will be rectangular. I imagine that this could be improved upon by creating a bitmap render of the content as highlighted in this blog - but this will likely be quite costly. I also have to ensure that the rectangle size and shape exactly matches that of my main content!
It feels very wrong that there is a rectangle drawn on the screen (even though hidden by my main content). The rectangle is purely there to create the alpha mask so I guess it could be scrapped if the mask is created from the renderof the content.
I've tried setting the visibility of the rectangle to collapsed to remove it from the visual tree. This means that I can attach the visual to the container instead:
ElementCompositionPreview.SetElementChildVisual(container, visual)
However, doing this means that the shadow displays in front of the main content, which means I need some other ui element to attach it too - may as well be the rectangle!
Your solution to use Rectangle is my current workaround everywhere I need rounded shadow under Grid or Border. It's simple and it's plain, why should I complain :)
But if it's not your choice you can draw a rounded rectangle and blur it:
GraphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(Compositor, CanvasDevice.GetSharedDevice());
var roudRectMaskSurface = GraphicsDevice.CreateDrawingSurface(new Size(SurfaceWidth + BlurMargin * 2, SurfaceHeight + BlurMargin * 2), DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied);
using (var ds = CanvasComposition.CreateDrawingSession(roudRectMaskSurface))
{
ds.Clear(Colors.Transparent);
ds.FillRoundedRectangle(new Rect(BlurMargin, BlurMargin, roudRectMaskSurface.Size.Width + BlurMargin, roudRectMaskSurface.Size.Height + BlurMargin), YourRadius, YourRadius, Colors.Black);
}
var rectangleMask = Compositor.CreateSurfaceBrush(roudRectMaskSurface);
Now you can apply this surface in the EffectBrush with blur effect to obtain custom shadow.
BlurMargin - corresponds to the blur amount, you need it because your blurred surface will be bigger than initial source rectangle (to avoid blur clip).
I am using Chart control from .NET framework in my project. I have added chart control to the form and configured as shown below.
// Add a new series.
chart1.Series.Add("1");
var series = chart1.Series[0];
series.ChartType = SeriesChartType.Spline;
// Hide the legend.
series.IsVisibleInLegend = false;
// configure x axis.
var cArea = chart1.ChartAreas[0];
cArea.AxisX.IntervalType = DateTimeIntervalType.Number;
cArea.AxisX.LabelStyle.Format = "00";
cArea.AxisY.LabelStyle.Format = "0.000";
cArea.AxisY.LabelStyle.IsEndLabelVisible = true;
cArea.AxisX.Minimum = 0;
cArea.AxisX.Maximum = 100;
cArea.AxisX.Interval = 20;
cArea.AxisY.Minimum = 0;
cArea.AxisY.Maximum = 100;
cArea.AxisX.Interval = 20;
Data point values are as below:
chart1.Series[0].Points.AddXY(0, 5);
chart1.Series[0].Points.AddXY(5, 10);
chart1.Series[0].Points.AddXY(10, 30);
chart1.Series[0].Points.AddXY(20, 100);
chart1.Series[0].Points.AddXY(30, 100);
chart1.Series[0].Points.AddXY(40, 90);
chart1.Series[0].Points.AddXY(50, 80);
For the above data points, series is not smooth. Upper edge is getting cut. Refer attached image.
How to make it smooth so that whole line is visible ?
It's not visible because of the smoothing, adapt the scale (using cArea.AxisX.Maximum = 150; for example) or remove the smoothing to make the whole curve visible.
As with the DrawCurves GDI+ method you can control the tension of the splines, i.e. how close they are to the points and their connecting lines and how much smoothing they create. Too much 'smoothing' creates the fantasy tops you see and also crazy whirls from even small bumps in the data..
Setting the tension it is done via the LineTension Custom attribute.
Lower it from the default of 0.8 to something smaller. Test to see what you prefer.
Here is an example for a Series S :
S.SetCustomProperty("LineTension", "0.4");
Note that you still should make the y-axis Maximum a little larger or else you may need to bring the tension down to 0, which will look like a line type..
Here are a few variations:
I'm building a user control using WPF to resemble a breadboard that will be used in an electronic circuits simulation software.
The user will be able to add ICs onto that breadboard.
I've finished building the breadboard right now, it consists mainly of a grid that has a number of cells equal to the number on nodes on a real breadboard..
And right now I made users add ICs to that grid by specifying the Grid.Row and Grid.Column and determining its size by specifying Grid.RowSpan and Grid.ColumnSpan:
Here is the C# code:
private int usedVerticalPins = 0;
void AddICs(int pinNum)
{
var rect = new Rectangle() { Fill = new SolidColorBrush(Colors.Black),Stroke = new SolidColorBrush(Colors.White)};
rect.SetValue(Grid.RowProperty, usedVerticalPins);
rect.SetValue(Grid.ColumnProperty, 7);
rect.SetValue(Grid.RowSpanProperty, (pinNum / 2));
rect.SetValue(Grid.ColumnSpanProperty, 3);
BreadboardControl.BreadboardGrid.Children.Add(rect);
usedVerticalPins += (pinNum / 2);
}
But as you can see, it looks like plain rectangles not as ICs, so I thought of inheriting the class Rectangle and edit the way it looks by I found that it is a sealed class..
I want the ICs to look like with all pins and stuff..
So can you please suggest a way to do it..!?
I have a Grid with a Adorner to provide some drawn pattern. See img: http://imgur.com/D649W
My problem is that this Adorner(dots on the Grid) is layered on top of everything. The white square are draggable but now when the Adorner are on top, I can't drag. I would like the layer to be behind every component added to the Grid. Any suggestions on how I can set the ZIndex?
Thanks.
Code below:
MyAdorner ad = new MyAdorner(grid);
AdornerLayer adLayer = AdornerLayer.GetAdornerLayer(grid);
adLayer.Add(ad);
I push my Button and this is adding the MyAdorner to the grid. MyAdorner looks like this:
public MyAdorner(Grid adornedGrid)
: base(adornedGrid) {
Height = adornedGrid.Height;
Width = adornedGrid.Width;
brush = new VisualBrush();
brush.Stretch = Stretch.Fill;
brush.TileMode = TileMode.Tile;
brush.Viewport = new Rect(0, 0, SnapDistance, SnapDistance);
brush.ViewportUnits = BrushMappingMode.Absolute;
brush.Viewbox = new Rect(0, 0, SnapDistance, SnapDistance);
brush.ViewboxUnits = BrushMappingMode.Absolute;
ellipse = new Ellipse() { Fill = new SolidColorBrush(Colors.Blue), Width = 2, Height = 2 };
brush.Visual = ellipse;
}
protected override void OnRender(System.Windows.Media.DrawingContext drawingContext) {
Pen renderPen = new Pen(new SolidColorBrush(Colors.Black), 0);
drawingContext.DrawRectangle(brush, renderPen, new Rect(new Point(0, 0), AdornedElement.DesiredSize));
}
If your problem is that the adorner is covering the elements you want to manipulate so that they become un-draggable etc, set .IsHitTestVisible = False on the adorner.
You can also set the adorner's opacity to some semi-transparent value to see the background through it if that is desirable.
Is this what you're looking for?
Panel.SetZIndex(ad, 20)
Attached properties of the framework are usually asignable from static methods of the UIElement that holds it.
EDIT:
Possible alternative: - make your own Panel
Easy and dirty way to make sure that your wanted elements are ALWAYS on top:
Declare a static in a Util library:
public static int ZIndexCount;
Then when you want an element on top you simply do:
SetZIndex(_viewbox, Util.ZIndexCount++);
Of course, if your application runs 5 years without being interrupted the ZIndexCount will go back to 0...
It works like a charm in my applications.
I know this is quite old but I thought I try anyway:
You can add a new AdornerDecorator to you visual tree hierarchy to render the controls at the right level. By default the root of the tree provides the AdornerDecorator but you can add as many as you want and your the components you add will be rendered in them. For more information - see here
<Grid>
<AdornerDecorator>
...your Adorners render here
</AdornerDecorator>
</Grid>
https://wangmo.wordpress.com/2008/10/19/relations-between-adorner-adornerlayer-and-adornerdecorator/
I would like to draw two shapes in WPF and merge them together. Then, I'd like to attach a drag/drop event to ONE of the original shapes.
So basically, you can only drag if you click on a certain part of the shape, but it will drag the entire shape with you.
Here is some code:
// Set up some basic properties for the two ellipses
Point centerPoint = new Point(100, 100);
SolidColorBrush ellipseColor_1 = new SolidColorBrush(Color.FromArgb(255, 0, 0, 255));
double width_1 = 10; double height_1 = 10;
SolidColorBrush ellipseColor_2 = new SolidColorBrush(Color.FromArgb(50, 255, 0, 0));
double width_2 = 200; double height_2 = 200;
// Create the first ellipse: A small blue dot
// Then position it in the correct location (centerPoint)
Ellipse ellipse_1 = new Ellipse() { Fill = ellipseColor_1, Width = width_1, Height = height_1 };
ellipse_1.RenderTransform = new TranslateTransform(point.X - width_1 / 2, point.Y - height_1 / 2);
// Create the second ellipse: A large red, semi-transparent circle
// Then position it in the correct location (centerPoint)
Ellipse ellipse_2 = new Ellipse() { Fill = ellipseColor_2, Width = width_2, Height = height_2 };
ellipse_2.RenderTransform = new TranslateTransform(point.X - width_2 / 2, point.Y - height_2 / 2);
// ???
// How should I merge these?
// ???
// Now apply drag drop behavior to ONLY ellipse_1
MouseDragElementBehavior dragBehavior = new MouseDragElementBehavior();
dragBehavior.Attach(ellipse_1); // This may change depending on the above
// ...
// Add new element to canvas
This code creates two circles (a big one and a small one). I would like to only be able to drag if the small one is clicked, but I'd like to have them attached so they'll move together without having to manually add code that will take care of this.
If you put them both in a Grid (or Canvas, StackPanel, etc.), and set the drag behavior on the panel, they will be "merged". If you set IsHitTestVisible to false on ellipse_2, it won't respond to any mouse events, so effectively it won't be draggable.