WPF - Adding ContentControl to Custom Canvas - wpf

I have a custom DrawingCanvas which is inherited from Canvas. When I add a ContentControl to DrawingCanvas with the following code nothing shows up.
GraphicsRectangle rect = new GraphicsRectangle(0, 0, 200, 200, 5, Colors.Blue);
DrawingContainer host = new DrawingContainer(rect);
ContentControl control = new ContentControl();
control.Width = 200;
control.Height = 200;
DrawingCanvas.SetLeft(control, 100);
DrawingCanvas.SetTop(control, 100);
control.Style = Application.Current.Resources["DesignerItemStyle"] as Style;
control.Content = host;
drawingCanvas.Children.Add(control);
GraphicsRectangle is a DrawingVisual and the constructor above draws a Rect with (0,0) top left point and length of 200 to the drawingContext of GraphicsRectangle. DrawingContainer is a FrameworkElement and it has one child, which is rect above, given with constructor. DrawingContainer implements GetVisualChild and VisualChildrenCount override methods. At last, Content property of ContentControl is set to the DrawingContainer to be able to show the DrawingVisual's content.
When I add the created ContentControl to a regular Canvas, control is showed correctly. I guess the reason is that DrawingCanvas doesn't implement ArrangeOverride method. It only implements MeasureOverride method. Also DrawingContainer doesn't implement Measure and
Arrange override methods. Any ideas?

As I thought the problem was missing ArrangeOverride method in DrawingCanvas. With the following ArrangeOverride method added to DrawingCanvas, ContentControls are showed correctly.
protected override Size ArrangeOverride(Size arrangeSize)
{
foreach (Visual child1 in children)
{
if (child1 is DrawingVisual)
continue;
ContentControl child = child1 as ContentControl;
GraphicsBase content = ((DrawingContainer)(child.Content)).GraphicsObject;
child.Arrange(new Rect(DrawingCanvas.GetLeft(child), DrawingCanvas.GetTop(child), content.Width, content.Height));
}
return arrangeSize;
}
where GraphicsBase is the base of the GraphicsRectangle class. In order to find the size of the GraphicsBase, I added width and height properties to GraphicsBase which are set in the constructor of GraphicsRectangle.

Related

How to programatically resize a DrawingVisual?

So, I'm new to WPF Drawing. For performance reasons, I've had to switch from regular controls like ContentControl and UserControl to more light-weight elements like DrawingVisual. I am working on a diagramming app which would probably have a max of 1000 elements on the canvas that can be dragged, resized and such. Firstly, is it better to use DrawingVisual instead of Shape?
Secondly, my main question here. I am adding DrawingVisual elements to the Canvas as such:
public class SVisualContainer : UIElement
{
// Create a collection of child visual objects.
private VisualCollection _children;
public SVisualContainer()
{
_children = new VisualCollection(this);
_children.Add(CreateDrawingVisualRectangle());
}
// Create a DrawingVisual that contains a rectangle.
private DrawingVisual CreateDrawingVisualRectangle()
{
DrawingVisual drawingVisual = new DrawingVisual();
// Retrieve the DrawingContext in order to create new drawing content.
DrawingContext drawingContext = drawingVisual.RenderOpen();
// Create a rectangle and draw it in the DrawingContext.
Rect rect = new Rect(new System.Windows.Point(160, 100), new System.Windows.Size(320, 80));
drawingContext.DrawRectangle(System.Windows.Media.Brushes.LightBlue, null, rect);
// Persist the drawing content.
drawingContext.Close();
return drawingVisual;
}
// Provide a required override for the VisualChildrenCount property.
protected override int VisualChildrenCount
{
get { return _children.Count; }
}
// Provide a required override for the GetVisualChild method.
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index >= _children.Count)
{
throw new ArgumentOutOfRangeException();
}
return _children[index];
}
}
And within the canvas:
public void AddStateVisual()
{
var sVisual = new SVisualContainer();
Children.Add(sVisual);
Canvas.SetLeft(sVisual, 10);
Canvas.SetTop(sVisual, 10);
}
How can I increase the size of the Rectangle dynamically through code? I have tried setting the Height and Width of the Rectangle which did not work, played around with the ScaleTransform but that is probably not what I want. Would I need to redraw the Rectangle? Thanks!
I ended up using DrawingVisual within UIElement as shown in the question, and continuously redrawing the DrawingVisual upon resize. The UIElement.RenderSize property, UIElement.MeasureCore method and UIElement.InvalidateMeasure method are central to this. This works quite well and the performance is acceptable.

In WPF, how to display AdornerLayer on top of DataGrid

I am using WPF datagrid from codeplex. I am using DatagridTemplateColumn and I have written datatemplates to display contents in each column.
Now I have to display some help message to a user when the any control in datagrid is focussed.
For this I thought of using adorner layer. I used ComboBox loaded event and accessed the adrorner layer of it. I then added my own adorner layer with some thing to be displayed there similar to tooltip. Below is the code.
TextBox txtBox = (TextBox)comboBox.Template.FindName("PART_EditableTextBox", comboBox);
if (txtBox == null)
return;
txtBox.ToolTip = comboBox.ToolTip;
AdornerLayer myAdornerLayer = AdornerLayer.GetAdornerLayer(txtBox);
Binding bind = new Binding("IsKeyboardFocused");
bind.Converter = new KeyToVisibilityConverter();
bind.Source = txtBox;
bind.Mode = BindingMode.OneWay;
PEAdornerControl adorner = new PEAdornerControl(txtBox);
adorner.SetBinding(PEAdornerControl.VisibilityProperty, bind);
PEAdorner layer is this ::
public class PEAdornerControl : Adorner
{
Rect rect;
// base class constructor.
public PEAdornerControl(UIElement adornedElement)
: base(adornedElement)
{ }
protected override void OnRender(DrawingContext drawingContext)
{
.....
}
}
Now the problem is as follows. I am attaching screenshot of how it is looking in datagrid. If the datagrid has more than 4 rows, things are fine.Below is the screenshot
If the datagrid has less number of row, this adorner goes inside datagrid and is not visible to user. The screenshot is below
How do I get this adorner layer above the DataGrid? Please help me !!!
I looked at your question again and i think this is what you would need.
TextBox txtBox = (TextBox)comboBox.Template.FindName("PART_EditableTextBox", comboBox);
if (txtBox == null)
return;
txtBox.ToolTip = comboBox.ToolTip;
//this is locating the DataGrid that contains the textbox
DataGrid parent = FindParent<DataGrid>(this);
//Get the adorner for the parent
AdornerLayer myAdornerLayer = AdornerLayer.GetAdornerLayer(parent);
Binding bind = new Binding("IsKeyboardFocused");
bind.Converter = new KeyToVisibilityConverter();
bind.Source = txtBox;
bind.Mode = BindingMode.OneWay;
PEAdornerControl adorner = new PEAdornerControl(txtBox);
adorner.SetBinding(PEAdornerControl.VisibilityProperty, bind);
The find parent method is this:
public T FindParent<T>(DependencyObject obj) where T : DepedencyObject
{
if (obj == null)
return null;
DependencyOBject parent = VisualTreeHelper.GetParent(obj);
if (parent is T)
return parent as T;
else
return FindParent<T>(parent);
}
You may need to set the position of your adorner in the OnRender method but this should work. One thing to consider though is that if your DataGrid is within another container (such as a panel, grid, etc) then you may still run into your clipping problem.
The clipping problem is due to the fact that when a container checks the layout of its children it does not normally take into account their adorners. To combat this you would possibly need to create your own control and override the MeasuerOverride(Size constraint) method.
Example:
public class MyPanel : Panel
{
protected override Size MeasureOverride(Size constraint)
{
Size toReturn = new Size();
foreach (UIElement child in this.InternalChildren)
{
//Do normal Measuring of children
foreach( UIElement achild in AdornerLayer.GetAdorners(child))
//Measure child adorners and add to return size as needed
}
return toReturn;
}
}
That code is really rough for measure but should point you in the right direction. Look at the documentation page http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.measureoverride.aspx for information about measuring child elements in a panel.
Just get the topmost AdornerLayer, instead
static AdornerLayer GetAdornerLayer(FrameworkElement adornedElement)
{
var w = Window.GetWindow(adornedElement);
var vis = w.Content as Visual;
return AdornerLayer.GetAdornerLayer(vis);
}
Also, if you have the name of your DataGrid you can get the nearest layer above it:
AdornerLayer myAdornerLayer = AdornerLayer.GetAdornerLayer(myDataGrid);

How to get a WPF Viewbox coefficient of scaling applied

In case we use WPF (Silverlight) Viewbox with Stretch="UniformToFill" or Stretch="Uniform" when it preserves content's native aspect ratio, how could we get knowing the current coefficient of scaling which were applied to the content?
Note: we not always know the exact initial dimensions of the content (for example it's a Grid with lots of stuff in it).
See this question: Get the size (after it has been "streched") of an item in a ViewBox
Basically, if you have a Viewbox called viewbox, you can get the ScaleTransform like this
ContainerVisual child = VisualTreeHelper.GetChild(viewbox, 0) as ContainerVisual;
ScaleTransform scale = child.Transform as ScaleTransform;
You could also make an extension method for Viewbox which you can call like this
viewbox.GetScaleFactor();
ViewBoxExtensions
public static class ViewBoxExtensions
{
public static double GetScaleFactor(this Viewbox viewbox)
{
if (viewbox.Child == null ||
(viewbox.Child is FrameworkElement) == false)
{
return double.NaN;
}
FrameworkElement child = viewbox.Child as FrameworkElement;
return viewbox.ActualWidth / child.ActualWidth;
}
}

WPF UserControl is not drawn when overriding MeasureOverride and ArrangeOverride

I have a UserControl looking like this:
<UserControl
MaxHeight="32"
MaxWidth="32"
MinHeight="25"
MinWidth="25">
<DockPanel>
<!-- some stuff -->
</DockPanel>
</UserControl>
In addition to the min/max size constraint, I want the control always being painted with Width = Height. So i override MeasureOverride and ArrangeOverride:
protected override Size MeasureOverride(Size availableSize)
{
var resultSize = new Size(0, 0);
((UIElement)Content).Measure(availableSize);
var sideLength = Math.Min(((UIElement)Content).DesiredSize.Width, ((UIElement)Content).DesiredSize.Height);
resultSize.Width = sideLength;
resultSize.Height = sideLength;
return resultSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
((UIElement)Content).Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height));
return finalSize;
}
I understand that I must call Measure and Arrange on every child of the UserControl. Since the DocPanel is the only child of my UserControl and (in my understanding) is stored in the Content property of the UserControl, I simply call Measure and Arrange on this Content property. However the UserControl is not displayed. What am I doing wrong?
Depending on how you are hosting your UserControl, the value returned from the Measure phase may not be used. If you have it setup in a Grid with star rows/columns or a DockPanel, then the final size may be completely different.
You would need to apply similar logic to the arrange phase, so it will effectively ignore any extra space it's given.
The following code should work, and is a bit cleaner:
protected override Size MeasureOverride(Size availableSize) {
var desiredSize = base.MeasureOverride(availableSize);
var sideLength = Math.Min(desiredSize.Width, desiredSize.Height);
desiredSize.Width = sideLength;
desiredSize.Height = sideLength;
return desiredSize;
}
protected override Size ArrangeOverride(Size finalSize) {
var sideLength = Math.Min(this.DesiredSize.Width, this.DesiredSize.Height);
return base.ArrangeOverride(new Size(sideLength, sideLength));
}

Positioning adorner relative to parent's dimensions in WPF

I am trying to position an Adorner depending on the dimensions of the parent of the adorned element. For example, I have a textbox. I want to adorn this textbox so it looks something like this:
how the adorner needs to be placed http://img707.imageshack.us/img707/9840/fig1.png
A textbox is placed in a canvas object and if there is enough space available then place the adorner (semi transparent rounded square) in line with the bottom edge of the textbox. The adorner is initiated when the user clicks on the textbox.
Currently the canvas and its contents (the textbox) is hosted in a WinForms form - so the WPF is handled by the ElementHost control.
But when I run my code, when the textbox is clicked for the first time it displays the adorner aligned to the top edge of the textbox (see figure below). After that it positions itself correctly (like the figure above) Does anyone know why this might be?
how adorner is positions http://img14.imageshack.us/img14/4766/fig2v.png
I have pasted the code for this below:
TextBoxAdorner.cs - this the adorner logic
public class TextBoxAdorner : Adorner
{
private TextBox _adornedElement;
private VisualCollection _visualChildren;
private Rectangle _shape;
private Canvas _container;
private Canvas _parentCanvas;
public TextBoxAdorner(UIElement adornedElement, Canvas parentCanvas)
: base(adornedElement)
{
_adornedElement = (TextBox)adornedElement;
_parentCanvas = parentCanvas;
_visualChildren = new VisualCollection(this);
_container = new Canvas();
_shape = new Rectangle();
_shape.Width = 100;
_shape.Height = 80;
_shape.Fill = Brushes.Blue;
_shape.Opacity = 0.5;
_container.Children.Add(_shape);
_visualChildren.Add(_container);
}
protected override Size ArrangeOverride(Size finalSize)
{
Point location = GetLocation();
_container.Arrange(new Rect(location, finalSize));
return finalSize;
}
private Point GetLocation()
{
if (_parentCanvas == null)
return new Point(0, 0);
Point translate;
double xloc = 0, yloc = _shape.Height - _adornedElement.ActualHeight;
if (yloc < 0) // textbox is bigger than the shape
yloc = 0;
else
{
translate = this.TranslatePoint(new Point(0, -yloc), _parentCanvas);
// coordinate is beyond the position of the parent canvas
if (translate.Y < 0) // this is true the first time it's run
yloc = 0;
else
yloc = -yloc;
}
translate = this.TranslatePoint(new Point(_shape.Width, 0), _parentCanvas);
// textbox is in right edge of the canvas
if (translate.X > _parentCanvas.ActualWidth)
{
double pos = translate.X - _parentCanvas.ActualWidth;
translate = this.TranslatePoint(new Point(-pos,0), _parentCanvas);
if (translate.X < 0)
xloc = 0;
else
xloc = translate.X;
}
return new Point(xloc, yloc);
}
protected override Size MeasureOverride(Size constraint)
{
Size myConstraint = new Size(_shape.Width, _shape.Height);
_container.Measure(myConstraint);
return _container.DesiredSize;
}
protected override Visual GetVisualChild(int index)
{
return _visualChildren[index];
}
protected override int VisualChildrenCount
{
get
{
return _visualChildren.Count;
}
}
}
The position of an Adorner is relative to the adorned element. If you want it to be to the top of your object, the value of yloc should be negative. However, the code you have also regards the boundaries of the Canvas. If there's not enough place for the rectangle above, it would put it below. Trying placing the TextBox a little lower in the Canvas.

Resources