I have created a custom user control, which is basicly a ItemsControl with it's ItemsPanelTemplate set to canvas.
On the mainpage I bind a List<Element> to it, where Element is a custom class.
However, all the controls are placed right on top of eachother. A easy way to fix this ofcourse is by making the Canvas a WrapPanel but I'm not sure if this will colide with the ability of Drag & Drop on the control
So my question would be, is it possible to have a property in the model Element which checks on which position it is of a list, if it is in a list?
something like:
public class Element
{
public int positionInList { get { return (this.IsInList) ? this.ListPosition : 0; } }
}
Update
What I wish to accomplish is that when the elements are added to the canvas, they automaticly pick their spot by 2 properties (which will be bound to the Canvas.Left and Canvas.Top or something similar)
public double GetX { get { return 50 * (Element.PositionInList % 5); } }
public double GetY { get { return 50 * (Element.PosotionInList / 5); } }
Without manually having to set the element's position in the list.
When you place elements in a Canvas, you must specify the coordinates where the elements will be placed (left upper corner for each element). Maybe is better to derivate from Canvas a new class that will perform the placement for you and use this class for ItemsPanelTemplate. Also, Drag & Drop should work with WrapPanel but depends on your requirements.
A quick and non optimal sample:
class CustomCanvas : Canvas
{
private int mChildsNum = 0;
...
CustomCanvas()
{
// Track changes that appear in canvas when new
// children are added
this.LayoutUpdated += CanvasChangeTracker;
}
...
private void CanvasChangeTracker(object source, EventArgs e)
{
if ( this.Children.Count != mChildsNum )
{
// A new child was added.
// Update the coordinates for children
mChildsNum = this.Children.Count;
}
}
...
}
This example can be considerably improved.
You need a custom panel, so you can create a class and inherits from Panel this will implements two methods to override
protected override Size MeasureOverride(Size availableSize)
protected override Size ArrangeOverride(Size finalSize)
Which you must implement.
Here is an example:
http://www.codeproject.com/Articles/31537/Custom-Panel-in-Silverlight-Advanced-Canvas
If you have properties GetX and GetY, you can do the same as the accepted answer here:
Silverlight 3 - Data Binding Position of a rectangle on a canvas
I would just reply to your post, since this is not really my answer, but I guess I don't have enough rep to reply.
Related
So the problem is that I created a custom Control which holds 3 other controls deriving from FrameworkElement:
class TextArea : Control {
private List<LocalViewBase> views;
private Views.TextView.View textView;
private Views.CaretView.View caretView;
private Views.SelectionView.View selectionView;
protected override int VisualChildrenCount => views.Count;
protected override Visual GetVisualChild(int index) => views[index];
public TextArea() {
views = new List<LocalViewBase>();
SetViews();
}
private void SetViews() {
textView = new Views.TextView.View() { Margin = new Thickness(EditorConfiguration.GetTextAreaLeftMargin(), 0, 0, 0) };
textInfo = new LocalTextInfo(textView);
selectionView = new Views.SelectionView.View(textInfo) { Margin = new Thickness(EditorConfiguration.GetTextAreaLeftMargin(), 0, 0, 0) };
caretView = new Views.CaretView.View(textInfo) { Margin = new Thickness(EditorConfiguration.GetTextAreaLeftMargin(), 0, 0, 0) };
foreach (var view in new LocalViewBase[] { selectionView, textView, caretView }) {
views.Add(view);
AddVisualChild(view);
AddLogicalChild(view);
}
}
}
public abstract class LocalViewBase : FrameworkElement { }
LocalViewBase is currently an empty class deriving from FrameworkElement.
The problem I'm dealing with right now is that only the OnRender of the earliest added child is being called - in this case selectionView - and only it is drawn with a proper margin. I tried #devhedgehog's answer from here: WPF - Visual tree not used for drawing? but it doesn't do the trick for me. Even if I extend the Panel class instead of Control and use its Children collection instead of calls to AddVisualChild() and AddLogicalChild(), still the views aren't draw with a proper margin. I also tried every method like InvalidateWhatever() but it also didn't help.
Probably the other views are not drawn with a correct margin, because all of them are stacked on top of each other and WPF "thinks" that only the selectionView is visible, so the actual question is - how do I convince it to think otherwise? :)
The solution for this problem was really simple. I had to extend StackPanel class and override the ArrangeOverride method (for each child to be put on x, y = 0, 0).
public abstract class StackablePanel : StackPanel {
protected override Size ArrangeOverride(Size arrangeSize) {
foreach (var child in Children) {
var uiElement = (UIElement)child;
var rcChild = new Rect(0, 0, Width, Height);
uiElement.Arrange(rcChild);
}
return arrangeSize;
}
}
In one of my previous approaches to this problem I extended the Panel class, but it was not enough - the default implementation for ArrangeOverride doesn't put child UIElement's as I would desire and it wasn't passing a correct Size instance to the child's Arrange method.
Now when my TextArea class extends this one, each of its child views is drawn correctly and its OnRender method is called.
I want to make some controls (specifically: Button, Label and Panel) to become dependent on the size of their parent. So a button might be 0.1 times the width of the parent, 0.05 the height, and be positioned in 0.3 times the width and 0.2 times the height.
Now I have 2 problems:
First I want to change the behaviour of the Control class into a sort of 'relative size and relative position'-Control. This would be very easy if the Control class had an 'onParentResized' method I could override, but it hasn't. So now my solution is this
class RelativeControl : Control
{
Control previousParent;
double relativeWidth, relativeHeight, relativeX, relativeY;
public RelativeControl(double RelativeWidth, double RelativeHeight, double RelativeX, double RelativeY)
{
// the arguments need to be between 0 and 1 normally, or the control is
// garanteed to be partially offscreen
this.relativeWidth = RelativeWidth;
this.relativeHeight = RelativeHeight;
this.relativeX = RelativeX;
this.relativeY = RelativeY;
}
protected override void OnParentChanged(EventArgs e)
{
if(previousParent != null)
{
previousParent.Resize -= new EventHandler(parentResized);
}
if(this.Parent != null)
{
this.Parent.Resize += parentResized;
}
this.previousParent = this.Parent;
}
private void parentResized(Object o, EventArgs e)
{
this.Width = (int)(this.Parent.Width * this.relativeWidth);
this.Height = (int)(this.Parent.Width * this.relativeHeight);
this.Location = new Point((int)(this.Parent.Width * this.relativeX), (int)(this.Parent.Height * this.relativeY));
}
}
Is this a good solution?
Second problem: I want to make the Button-class (as well as the Panel and Label class) to extend this new version of control. However this isn't possible as far as I know. My only option seems to be to make 3 classes and literally find-and-replace 'Control' by "Label", "Button" and "Panel" to get the result I want.
What should I be doing here?
I think you are after TableLayoutPanel control.
Happily on .NET platform do not have to worry anymore about child controls resizing or repositioning to the parent.
You have to make extensive use of the Dock and Anchor properties of the child controls.
You can start with the links but there are many tutorials about them on the web.
TableLayoutPanel
Anchor and Dock Child Controls
Create a Resizable Windows Form
What control should I use to make a list of items display? I want to be able to adjust the height of the item so its height can take up more space then another item in the same control list of items.
I looked at listboxes, but you can't adjust the size of the items. I've considered making blank entries for placeholders to be grouped as the same item but would rather not if possible.
What this control is to be used for is to represent chunks of time from the beginning of the day (the top) to the end of the day (the bottom).
I looked at listboxes, but you can't adjust the size of the items
But you can. Set the DrawMode property to OwnerDrawVariable.
You of course need to tell it how tall each item needs to be, that requires implementing the MeasureItem event. And of course you need to draw it, so you fill up the space that you reserved with MeasureItem, that requires implementing the DrawItem event. You'll find an excellent example in the MSDN Library article for MeasureItem.
A custom panel does it like a charm:
public class AutoPanel : Panel
{
public AutoPanel()
{
AutoScroll = true;
}
private int _nextOffset = 0;
public int ItemMarginX = 5;
public int ItemMarginY = 5;
public void Add(Control child)
{
child.Location = new Point(ItemMarginX, _nextOffset);
_nextOffset += (child.Height + ItemMarginY);
Controls.Add(child);
}
}
Add it to your form and add items to it like this:
panel1.Add(new Button { Text = "smaller item", Height = 20 });
panel1.Add(new Button { Text = "medium item", Height = 23 });
panel1.Add(new Button { Text = "larger item", Height = 32 });
When I add my UserControls to a FlowLayoutPanel, they display properly. When I change the Dock or Anchor properties on the UserControls before adding them, they are still added but do not render.
According to "How to: Anchor and Dock Child Controls" this should be possible.
I can tell that the controls are added (despite not drawing) because adding enough of them causes a vertical scrollbar to appear.
Setting the "Dock" property of the UserControls to "Left" or "None" will cause them to render, but none of the other options.
Setting the "Anchor" property on the UserControls to anything but Top | Left does not render.
Setting the dock before or after adding the control makes no difference (Add, Dock vs. Dock, Add).
The FlowLayoutPanel is itself is docked (Fill), has FlowDirection set to TopDown, has WrapContents set to false, has AutoScroll set to true, and is otherwise default.
I am using .NET 3.5.
In answer to a comment, the two commented lines are the locations I tried to change the dock. The second spot definitely makes more sense, but I tried the other because it couldn't hurt.
public void CreateObjectControl( object o )
{
ObjectControl oc = new ObjectControl();
oc.MyObject = o;
//This was a spot I mentioned:
//oc.Dock = DockStyle.Fill;
ObjectDictionary.Add( o, oc );
flowLayoutPanel1.Controls.Add( oc );
//This is the other spot I mentioned:
oc.Dock = DockStyle.Fill;
}
try using SuspendLayout and Resumelayout function for the controls before making any amendments which need rendering for proper viewing.
You could see the code from Designer.cs for that particular control
Syntax
control.SuspendLayout();
{Your code for designer amendments}
control.resumeaLayout();
I think I may have found a workaround (read: dirty trick) ... this answer helped to point me in the right direction. Here's an excerpt from the MS article that you also linked to:
For vertical flow directions, the FlowLayoutPanel control calculates the width of an implied column from the widest child control in the column. All other controls in this column with Anchor or Dock properties are aligned or stretched to fit this implied column.
The behavior works in a similar way for horizontal flow directions. The FlowLayoutPanel control calculates the height of an implied row from the tallest child control in the row, and all docked or anchored child controls in this row are aligned or sized to fit the implied row.
This page does not specifically mention that you can't Dock/Anchor the tallest/widest control. But as this control defines the layout behaviour of the FlowLayoutPanel, and thus influences the way all other sibling controls are displayed, it is well possible that Dock and Anchor don't work properly for that 'master control'. Even though I can't find any official documentation regarding that, I believe it to be the case.
So, which options do we have? At runtime, we could add a panel control of height 0 and width of the FlowLayoutPanel client area before you add your usercontrol. You can even set that panel's visibility to false. Subscribing to some Resize/Layout events of the FlowLayoutPanel to keep that panel's size will to the trick. But this does not play nicely at design time. The events won't fire and thus you can't really design the surface the way you want it to look.
I'd prefer a solution that "just works" at design time as well. So, here's an attempt at an "invisible" control that I put together, to fix the controls resizing to zero width if no other control is present. Dropping this as first control onto the FlowLayoutPanel at design time seems to provide the desired effect, and any control subsequently placed on the FlowLayoutPanel is anchorable to the right without shrinking to zero width. The only problem is that, once this invisible control is there, it seems I can't remove it anymore via the IDE. It probably needs some special treatment using a ControlDesigner to achieve that. It can still be removed in the form's designer code though.
This control, once placed onto the FlowLayoutPanel, will listen for resize events of it's parent control, and resize itself according to the ClientSize of the parent control. Use with caution, as this may contain pitfalls that didn't occur to me during the few hours I played with this. For example, I didn't try placing controls that were wider than the FlowLayoutPanel's client area.
As a side note, what will still fail is trying to anchor to the bottom, but that wasn't part of the question ;-)
using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Windows.Forms;
namespace ControlTest
{
public sealed class InvisibleControl : Control
{
public InvisibleControl()
{
TabStop = false;
}
#region public interface
// Reduce the temptation ...
public new AnchorStyles Anchor
{
get { return base.Anchor; }
set { base.Anchor = AnchorStyles.None; }
}
public new DockStyle Dock
{
get { return base.Dock; }
set { base.Dock = DockStyle.None; }
}
// We don't ever want to move away from (0,0)
public new Point Location
{
get { return base.Location; }
set { base.Location = Point.Empty; }
}
// Horizontal or vertical orientation?
private Orientation _orientation = Orientation.Horizontal;
[DefaultValue(typeof(Orientation), "Horizontal")]
public Orientation Orientation
{
get { return _orientation; }
set
{
if (_orientation == value) return;
_orientation = value;
ChangeSize();
}
}
#endregion
#region overrides of default behaviour
// We don't want any margin around us
protected override Padding DefaultMargin => Padding.Empty;
// Clean up parent references
protected override void Dispose(bool disposing)
{
if (disposing)
SetParent(null);
base.Dispose(disposing);
}
// This seems to be needed for IDE support, as OnParentChanged does not seem
// to fire if the control is dropped onto a surface for the first time
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
ChangeSize();
}
// Make sure we don't inadvertantly paint anything
protected override void OnPaint(PaintEventArgs e) { }
protected override void OnPaintBackground(PaintEventArgs pevent) { }
// If the parent changes, we need to:
// A) Unsubscribe from the previous parent's Resize event, if applicable
// B) Subscribe to the new parent's Resize event
// C) Resize our control according to the new parent dimensions
protected override void OnParentChanged(EventArgs e)
{
base.OnParentChanged(e);
// Perform A+B
SetParent(Parent);
// Perform C
ChangeSize();
}
// We don't really want to be resized, so deal with it
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
ChangeSize();
}
#endregion
#region private stuff
// Make this a default handler signature with optional params, so that this can
// directly subscribe to the parent resize event, but also be called without parameters
private void ChangeSize(object sender = null, EventArgs e = null)
{
Rectangle client = Parent?.ClientRectangle ?? new Rectangle(0, 0, 10, 10);
Size proposedSize = _orientation == Orientation.Horizontal
? new Size(client.Width, 0)
: new Size(0, client.Height);
if (!Size.Equals(proposedSize)) Size = proposedSize;
}
// Handles reparenting
private Control boundParent;
private void SetParent(Control parent)
{
if (boundParent != null)
boundParent.Resize -= ChangeSize;
boundParent = parent;
if (boundParent != null)
boundParent.Resize += ChangeSize;
}
#endregion
}
}
hi I have a C# app that features a canvas. I'd like to programmatically place a textbox (with text) on it. I've tried and tried but all I get is a fully transparent rectangle where my textbox ought to be. is it me or is this a known difficulty?
UPDATE:
I should have mentioned.. (Sorry!) I'm also overriding OnRender in the object to be drawn like so:
protected override void OnRender(DrawingContext drawingContext)
{
drawingContext.PushTransform(TransformRotation);
Draw(drawingContext);
drawingContext.Pop();
}
and Draw is implemented like so:
public override void Draw(DrawingContext drawingContext)
{
Rect graphicRectangle = Rectangle;
ITransform2d transformToDisplay = Layer.TransformToDisplay;
if (transformToDisplay != null)
{
graphicRectangle = new Rect(transformToDisplay.Transform(Rectangle.TopLeft),
transformToDisplay.Transform(Rectangle.BottomRight));
}
textBox.Height = graphicRectangle.Height;
textBox.Width = graphicRectangle.Width;
Canvas.SetLeft(textBox, graphicRectangle.Left);
Canvas.SetTop(textBox, graphicRectangle.Top);
}
Canvas being a panel whose purpose is to arrange and display some kind of content i would recommend that you do not do anything like this.
If you need a Canvas with a TextBox use composition, for example create a UserControl with a TextBox over or under the Canvas and expose relevant properties and methods on the UserControl's interface.
Use Panel elements to position and arrange child objects in Windows Presentation Foundation (WPF) applications. - MSDN