You can make custom panels in UWP XAML by deriving from the Panel class and overriding MeasureOverride and ArrangeOverride. How would I go about doing the same thing for a panel with just one child, similar to the Border control? Unfortunately, I cannot derive from Border because it's sealed.
I tried this but it doesn't work (of course, this is a simplified sample for what I'm trying to achieve):
public sealed class ProportionedPresenter : FrameworkElement
{
public static readonly DependencyProperty ChildProperty = DependencyProperty.Register(
"Child",
typeof(object),
typeof(ProportionedPresenter),
new PropertyMetadata(null));
public object Child
{
get { return this.GetValue(ProportionedPresenter.ChildProperty); }
set { this.SetValue(ProportionedPresenter.ChildProperty, value); }
}
protected override Size MeasureOverride(Size availableSize)
{
var minDimension = Math.Min(availableSize.Width, availableSize.Height);
var desiredSize = new Size(minDimension, minDimension);
if (this.Child is UIElement childElement)
{
childElement.Measure(desiredSize);
var maxDimension = Math.Max(childElement.DesiredSize.Width, childElement.DesiredSize.Height);
desiredSize = new Size(maxDimension, maxDimension);
}
return desiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
if (this.Child is UIElement childElement)
{
childElement.Arrange(new Rect(new Point(), childElement.DesiredSize));
}
return finalSize;
}
}
Related
I'm making a custom tick bar indicator, derived from the FrameworkElement class. And I have a bunch of properties (with accompanying dependency properties) defining the look and functionality of the indicator, e.g. Orientation and Range. Orientation is of type Orientation, and Range is of type double.
Changing Orientation from the view model causes the MeasureOverride() method to be called, allowing me to define the new dimensions of the control. But for some reason, changing the Range property doesn't (and neither does changing other double typed properties). And I can't figure out why. Anybody know why this is, and how to get the Range property to also call MeasureOverride()?
public class DepthIndicatorTickBar : FrameworkElement
{
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation", typeof(Orientation),
typeof(DepthIndicatorTickBar),
new FrameworkPropertyMetadata(Orientation.Horizontal,
FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty RangeProperty =
DependencyProperty.Register("Range", typeof(double),
typeof(DepthIndicatorTickBar),
new FrameworkPropertyMetadata((double)100,
FrameworkPropertyMetadataOptions.AffectsRender));
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
public double Range
{
get { return (double)GetValue(RangeProperty); }
set { SetValue(RangeProperty, value); }
}
protected override Size MeasureOverride(Size availableSize)
{
Console.WriteLine("MeasureOverride");
// Calculating new size
return size;
}
protected override void OnRender(DrawingContext drawingContext)
{
Console.WriteLine("OnRender");
// Drawing control
}
}
If you want to make sure that a value change of your dependency property forces a measure pass and re-rendering, just combine the appropriate FrameworkPropertyMetadataOptions flags:
public static readonly DependencyProperty RangeProperty = DependencyProperty.Register(
nameof(Range),
typeof(double),
typeof(DepthIndicatorTickBar),
new FrameworkPropertyMetadata(
100d,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender));
that will automatically add children into separate row. So I make own grid, add method to add new rows and handle it to OnVisualChildrenChanged. But anytime I'm adding new UIElement into it, RowDefinitions.IsReadOnly property is true and I cannot do that.
public class GridWithAutoRows : Grid
{
public void AddRow()
{
this.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
}
protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
{
this.AddRow();
Grid.SetRow(visualAdded as UIElement, this.RowDefinitions.Count - 1);
base.OnVisualChildrenChanged(visualAdded, visualRemoved);
}
}
Thanks for any idea.
I have added a margin (For adding breakpoints) to the left side of my TextEditor in the following manner:
public partial class LogicSimViewCodeWPFCtrl : UserControl
{
private class BreakPointMargin : AbstractMargin
{
private const int margin = 20;
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
return new PointHitTestResult(this, hitTestParameters.HitPoint);
}
protected override Size MeasureOverride(Size availableSize)
{
return new Size(margin, 0);
}
}
}
private void LogicCodeInit()
{
try
{
TxtEditCodeViewer.TextArea.LeftMargins.Insert(0, new BreakPointMargin());
...
The margin is added successfully but now I'd like to color the background of the margin. How can I accomplish this?
https://web.archive.org/web/20190716171503/http://community.sharpdevelop.net/forums/t/16047.aspx
You would have to override OnRender:
protected override void OnRender(DrawingContext drawingContext)
{
Size renderSize = this.RenderSize;
drawingContext.DrawRectangle(SystemColors.ControlBrush, null,
new Rect(0, 0, renderSize.Width, renderSize.Height));
Also, you aren't required to derived from AbstractMargin - you can use any WPF control you want. AbstractMargin just provides the TextView and Document properties and keeps them up to date. If you don't need those or can implement them yourself, you can just use another base class.
I am trying to add an simple Textblock as adorment to a control. But I want it to be positionned just above my adorned control.
This is the decoration creation ( the problem doesnt rely in this code):
public void AddLabelDecoration()
{
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this);
TextBlock textBlockMarkTooltipContent = new TextBlock();
textBlockMarkTooltipContent.Text = "Test Label Adorner";
_labelAdornerMarkTooltipContentAdorner = new Adorner(this)
{
Child = textBlockMarkTooltipContent
};
adornerLayer.Add(_labelAdornerMarkTooltipContentAdorner);
}
What I cannot achieve to do, is the positionning of the Decoration, above the adorned control. I would like to use this MSDN code sample, which makes use of AdornerPanel so as to do the positionning...
However I have not figured out how to access to an AdornerPanel object so as to apply this MSDN code sample... neither from my adorned control, from the AdornedLayout, or the Adorner...
I admit I don't clear understand the WPF class hierarchy between AdornerPanel and AdornerLayout.
Any help appreciated.
public void AddLabelDecoration()
{
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this);
TextBlock textBlockMarkTooltipContent = new TextBlock();
textBlockMarkTooltipContent.Text = "Test Label Adorner";
AdornerPanel labelAdornerAdornerPanel = new AdornerPanel();
// add your TextBlock to AdornerPanel
labelAdornerAdornerPanel.Children.Add(textBlockMarkTooltipContent);
// set placements on AdornerPanel
AdornerPlacementCollection placement = new AdornerPlacementCollection();
placement.PositionRelativeToAdornerHeight(-1, 0);
placement.PositionRelativeToAdornerWidth(1, 0);
AdornerPanel.SetPlacements(labelAdornerAdornerPanel, placement);
// create Adorner with AdornerPanel inside
_labelAdornerMarkTooltipContentAdorner = new Adorner(this)
{
Child = labelAdornerAdornerPanel
};
adornerLayer.Add(_labelAdornerMarkTooltipContentAdorner);
}
In order to move your Adorner you have to override the ArrangeOverride method and adjust a new adorner position there.
Here's an example with a simple FrameworkElementAdorner.
public class FrameworkElementAdorner : Adorner
{
private FrameworkElement _child;
public FrameworkElementAdorner(UIElement adornedElement)
: base(adornedElement)
{
}
protected override int VisualChildrenCount
{
get { return 1; }
}
public FrameworkElement Child
{
get { return _child; }
set
{
if (_child != null)
{
RemoveVisualChild(_child);
}
_child = value;
if (_child != null)
{
AddVisualChild(_child);
}
}
}
protected override Visual GetVisualChild(int index)
{
if (index != 0) throw new ArgumentOutOfRangeException();
return _child;
}
protected override Size MeasureOverride(Size constraint)
{
_child.Measure(constraint);
return _child.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
// Adjust your offset here:
_child.Arrange(new Rect(new Point(-20, -20), finalSize));
return new Size(_child.ActualWidth, _child.ActualHeight);
}
Usage:
TextBlock textBlockMarkTooltipContent = new TextBlock();
textBlockMarkTooltipContent.Text = "Test Label Adorner";
var adorner = new FrameworkElementAdorner(this)
{
Child = textBlockMarkTooltipContent
};
I'm trying to create a diagramming application in C# / WPF. What I going for is somewhat similar to Microsoft Visio although I'm not trying to clone it. I kind of wrote this question as I was coding and just put all the issues I had into it in case someone will find it useful. Maybe I've been thinking too hard but I feel like I could throw up on my keyboard and produce better code, so feel free to give any suggestions on every detail you catch (grammar excluded :-))
In short:
Why are all the items positioned at (0,0)?
Code:
public class Diagram : MultiSelector
{
public Diagram()
{
this.CanSelectMultipleItems = true;
// The canvas supports absolute positioning
FrameworkElementFactory panel = new FrameworkElementFactory(typeof(Canvas));
this.ItemsPanel = new ItemsPanelTemplate(panel);
// Tells the container where to position the items
this.ItemContainerStyle = new Style();
this.ItemContainerStyle.Setters.Add(new Setter(Canvas.LeftProperty, new Binding("X")));
this.ItemContainerStyle.Setters.Add(new Setter(Canvas.TopProperty, new Binding("Y")));
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
FrameworkElement contentitem = element as FrameworkElement;
Binding leftBinding = new Binding("X");
Binding topBinding = new Binding("Y");
contentitem.SetBinding(Canvas.LeftProperty, leftBinding);
contentitem.SetBinding(Canvas.TopProperty, topBinding);
base.PrepareContainerForItemOverride(element, item);
}
public class DiagramItem : ContentControl
{
private Point _location;
public DiagramItem()
{
}
static DiagramItem()
{
}
public Point Location
{
get { return _location; }
set
{
_location = value;
}
}
public double X
{
get { return _location.X; }
set
{
_location.X = value;
}
}
public double Y
{
get { return _location.Y; }
set
{
_location.Y = value;
}
}
}
//...
Ok, so the idea is that the Diagram : ItemsControl places its item on a Canvas panel at the position defined in the Item DiagramItem.Location. IOW when I change the X property in a DiagramItem the Diagram moves the item on the x-axis.
Note: MultiSelector is derived from ItemsControl and Selector and is only used here because I need the displayed item to be selectable.
Please note that I'd prefer not to use xaml if possible.
In long:
A Diagram instance as seen by the user has these requirements:
Has multiple DiagramItems.
User can select multiple DiagramItems.
DiagramItems can be resized, rotated and dragged anywhere on the Diagram.
Possible to navigate between DiagramItems using the keyboard.
I basically have two and possibly three classes relevant to this question.
Diagram extends System.Windows.Controls.Primitives.MultiSelector : Selector : ItemsControl
DiagramItem extends ContentControl or some other Control
The Diagram.ItemsPanel aka the visual panel which displays the items should be a panel which supports absolute positioning, like the Canvas.
How should I implement a class derived from MultiSelector and what resources can you point at which are relevant to this question?
What does one have to consider when implementing a custom MultiSelector / ItemsControl?
Resources:
I've found very few resources relevant to my issue, but then again I'm not sure what I'm supposed to be looking for. I've read the source code for ListBox and ListBoxItem using Reflector but didn't find it very useful.
Other resources:
System.Windows.Controls.Primitives.MultiSelector
System.Windows.Controls.ItemsControl
ItemsControl.ItemsPanel
System.Windows.Controls.Canvas
Positioning items when Canvas is the ItemsPanel of a ItemsControl
Using Templates to customize WPF controls
Create an items control
OK, apparently this can easily be achieved by using bindings and the property framework.
public class Diagram : MultiSelector
{
public Diagram()
{
this.CanSelectMultipleItems = true;
// The canvas supports absolute positioning
FrameworkElementFactory panel = new FrameworkElementFactory(typeof(Canvas));
this.ItemsPanel = new ItemsPanelTemplate(panel);
// Tells the container where to position the items
this.ItemContainerStyle = new Style();
this.ItemContainerStyle.Setters.Add(new Setter(Canvas.LeftProperty, new Binding("X")));
this.ItemContainerStyle.Setters.Add(new Setter(Canvas.TopProperty, new Binding("Y")));
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
FrameworkElement contentitem = element as FrameworkElement;
Binding leftBinding = new Binding("XProperty");
leftBinding.Source = contentitem;
Binding topBinding = new Binding("YProperty");
topBinding.Source = contentitem;
contentitem.SetBinding(Canvas.LeftProperty, leftBinding);
contentitem.SetBinding(Canvas.TopProperty, topBinding);
base.PrepareContainerForItemOverride(element, item);
}
public class DiagramItem : ContentControl
{
public static readonly DependencyProperty XProperty;
public static readonly DependencyProperty YProperty;
public static readonly RoutedEvent SelectedEvent;
public static readonly RoutedEvent UnselectedEvent;
public static readonly DependencyProperty IsSelectedProperty;
public DiagramItem()
{
}
static DiagramItem()
{
XProperty = DependencyProperty.Register("XProperty", typeof(Double), typeof(DiagramItem));
YProperty = DependencyProperty.Register("YProperty", typeof(Double), typeof(DiagramItem));
SelectedEvent = MultiSelector.SelectedEvent.AddOwner(typeof(DiagramItem));
UnselectedEvent = MultiSelector.SelectedEvent.AddOwner(typeof(DiagramItem));
IsSelectedProperty = MultiSelector.IsSelectedProperty.AddOwner(typeof(DiagramItem));
}
public Double X
{
get
{
return (Double)this.GetValue(XProperty);
}
set
{
this.SetValue(XProperty, value);
}
}
public Double Y
{
get
{
return (Double)this.GetValue(YProperty);
}
set
{
this.SetValue(YProperty, value);
}
}
public Point Location
{
get
{
return new Point(X, Y);
}
set
{
this.X = value.X;
this.Y = value.Y;
}
}
}
The magic is in the proper usage of Bindings, the key was to add the contentitem as Source. The next step is obviously to handle the selection of items, but that's another question on its own.
If it is any help, I wrote a code project article based on my graphing and diagramming custom control called NetworkView:
http://www.codeproject.com/Articles/182683/NetworkView-A-WPF-custom-control-for-visualizing-a