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
Related
I'm trying to create a custom container as UserControl.
My Goal: I want to be able to drag controls inside the designer and handle incoming controls inside the code of my usercontrol.
Example: I place my container somewhere and then add a button. In this momemt I want my usercontrol to automatically adjust the width and position of this button. Thats the point where Im stuck.
My code:
[Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design", typeof(IDesigner))]
public partial class ContactList : UserControl
{
public ContactList()
{
InitializeComponent();
}
private void ContactList_ControlAdded(object sender, ControlEventArgs e)
{
e.Control.Width = 200; // Nothing happens
e.Control.Height = 100; // Nothing happens
MessageBox.Show("Test"); // Firing when adding a control
}
}
The MessageBox is working well. The set width and height is ignored.
The question is just "why?".
EDIT
I've just noticed, when placing the button and recompiling with F6 the button gets resized to 200x100. Why isnt this working when placing?
I mean... the FlowLayoutPanel handles added controls right when you place it. Thats the exact behaviour im looking for.
Using OnControlAdded
To fix your code, when you drop a control on container and you want to set some properties in OnControlAdded you should set properties using BeginInvoke, this way the size of control will change but the size handles don't update. Then to update the designer, you should notify the designer about changing size of the control, using IComponentChangeService.OnComponentChanged.
Following code executes only when you add a control to the container. After that, it respects to the size which you set for the control using size grab handles. It's suitable for initialization at design-time.
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
if (this.IsHandleCreated)
{
base.BeginInvoke(new Action(() =>
{
e.Control.Size = new Size(100, 100);
var svc = this.GetService(typeof(IComponentChangeService))
as IComponentChangeService;
if (svc != null)
svc.OnComponentChanged(e.Control,
TypeDescriptor.GetProperties(e.Control)["Size"], null, null);
}));
}
}
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.
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
}
}
I wanted my panel to be truly transparent, so I followed the instructions on this article:
https://web.archive.org/web/20121226091810/http://www.bobpowell.net/transcontrols.htm
However, I've forced my panel to always show the Vertical scroll bar. It initially is not rendered unless I hover my mouse cursor over it, at which point it begins to appear. What more can be added to the above article to ensure that my panel's scrollbars are always visible, other than setting VerticalScroll.Visible to true?
Here is what I have so far for my custom Panel class. This is using C# .NET 4.0 in Visual Studio 2010:
public class SkinnedList : Panel
{
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x00000020; // WS_EX_TRANSPARENT
return cp;
}
}
public SkinnedList()
{
AdjustFormScrollbars( true );
}
public new void AdjustFormScrollbars( bool visible )
{
VerticalScroll.Visible = true;
HorizontalScroll.Visible = false;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// Do not render background
}
protected void InvalidateEx()
{
if( Parent != null )
{
Rectangle rc = new Rectangle( Location, Size );
Parent.Invalidate( rc, true );
}
}
}
The InvalidateEx() method isn't correct, you need to map the rectangle from panel coordinates to parent coordinates. Like this:
protected void InvalidateEx() {
if (Parent != null) {
Rectangle rc = new Rectangle(0, 0, this.ClientSize.Width - SystemInformation.VerticalScrollBarWidth, this.ClientSize.Height);
rc = this.RectangleToScreen(rc);
rc = Parent.RectangleToClient(rc);
Parent.Invalidate(rc, false);
}
}
The best way to get the scroll bar is to use the AutoScrollMinSize property:
public SkinnedList() {
this.AutoScroll = true;
this.AutoScrollMinSize = new Size(0, 1000);
this.Scroll += delegate { this.InvalidateEx(); };
}
This should fix your problems, except one. You'll notice the effect of the "Show window content while dragging" system option. It is best described as 'doing the pogo'. No fix for this, you cannot reasonably turn the system option off. This just can't work well.
That's the window style you picked. The control is transparent, meaning if not active it should blend in to the background, and only be interactive when needed.
Some suggestions, I don't know if any of them will work for your case:
Nest your transparent Panel in a normal Panel, and have the transparent one AutoSize itself to fit its contents. Then, the scrollbars will be on the containing Panel and will scroll the autosized, transparent Panel. This keeps the scrollbars always visible as needed, but may spoil your transparent effect.
Put your transparent Panel in a UserControl that has navigation buttons (up, down, pageup, pagedown, etc) that will trigger events on the transparent Panel (Scroll is the big one). This will require your transparent Panel to have handlers for the button click events, in which it will call its own OnScroll() method. This won't look like a standard scrollbar, and you won't be able to click and drag (unless you use a slider), but you can get around easily enough.
We have a form which displays media items in tab pages of a tab control, and I'm implementing a feature which allows users to 'pop out' the tab pages into their own forms.
However, when I add the media player to a form rather than a TabPage, the background switches from the gradient fill of a tab page to the plain SystemColors.Control background of the parent form. I need to add the the media player to a control which has the same background as a TabControl, but which doesn't display a tab at the top. I tried adding the media player to the TabControl's control collection, but that just throws an exception.
How do I get a control which looks like a TabControl with no tabs? Should I keep trying to add the media player to a TabControl, or should I try to write a Panel with a custom-drawn background? If the latter, how do I make sure that works with all possible themes?
The questions seems to be about the UseVisbleBackgroundStyle. AFAIK only buttons and TabPages have this property.
The following is a very dirty hack, just to get you started:
1) derive a customControl from Panel and add "using System.Windows.Forms.VisualStyles;"
2) Add the following code
//warning: incomplete, add error checking etc
private readonly VisualStyleElement element = VisualStyleElement.Tab.Body.Normal;
public bool UseVisbleBackgroundStyle { get; set; }
protected override void OnPaint(PaintEventArgs pe)
{
if (UseVisbleBackgroundStyle)
{
var x = new VisualStyleRenderer(element);
x.DrawBackground(pe.Graphics, this.ClientRectangle);
}
else
{
base.OnPaint(pe);
}
}
Thanks to Henk - I eventually went with:
protected override void OnPaintBackground(PaintEventArgs e)
{
if (TabRenderer.IsSupported && Application.RenderWithVisualStyles)
{
TabRenderer.DrawTabPage(pe.Graphics, this.ClientRectangle);
}
else
{
base.OnPaintBackground(pe);
ControlPaint.DrawBorder3D(pe.Graphics, this.ClientRectangle, Border3DStyle.Raised);
}
}
Try creating your own customer UserControl
This answer is modified from another answer site. It does the trick rather cleanly.
In the load event for the window containing the tab control, try:
// TabControl is the name of the tab control in this window.
TabControl.Appearance = TabAppearance.FlatButtons;
TabControl.Multiline = false;
TabControl.SizeMode = TabSizeMode.Fixed;
TabControl.ItemSize = new Size(0,1);
// The tabs are now gone. Select the panel you want to display
TabControl.SelectTab("InProgressTab");