I am designing a control where the user can specify the X, Y, Width and Height of the DisplayRectangle property in relation to the ClientRectangle.
From what I have read in the MSDN documentation, DisplayRectangle only has a get accessor, and therefore its dimensions cannot be set, however this is imperative in my control!
Can any one suggest how I might safely implement a DisplayRectangle that has both get and set accessors? - or explain why this is bad practice?
Thanks.
TESTS:
Set TabControl style to UserPaint, and adjust the Alignment property, The DisplayRectangle moves to compensate for the location of the tabs. Assuming TabControl has a built in mechanism to set the Rectangle bounds.
Created DemoControl : Control, painted ClientRectangle in Red, and DisplayRectangle in Blue and tried calling SetDisplayRectLocation(x, y)...not quite what I wanted...and yielded no results!
In order to allow the user to set the bounds of the DisplayRectangle property for a control, I have come up with the following solution:
public class ExtendedControl : Control
{
private Rectangle displayRectangle;
protected override Rectangle DisplayRectangle
{
get { return this.displayRectangle; }
}
public void SetDisplayRectangle(Rectangle rect)
{
this.displayRectangle = rect;
}
public ExtendedControl()
{
this.displayRectangle = base.DisplayRectangle;
}
}
Related
EDIT: For new programmers to OOP just calling the class isn't enough if the syntax is looking for a specific variable type it will throw an error. This was the problem as pointed at the answers bellow, this line of code:
System.Windows.Controls.Canvas.SetLeft(Gate_list[Gate_list.Count - 1], Pos.X);
It has one fatal mistake and that is that the Canvas.SetLeft is looking for a canvas child class (in this case a rectangle) and a point. However I sent in a Class type Gate_list[Gate_list.Count - 1] when it should've been Gate_list[Gate_list.Count - 1].GetRect()
End of edit
I have a delegate that is calling a class in a method. The delegate is detecting a mouse down event on a Rectangle(Here is how it's done). In the method I'm trying to SetLeft on a Rectangle I just added to the Canvas but get the error CS1503.
I've tried converting it to System.Windows.UIElement but System can't be converted.
public partial class Program
{
public void Rect_Button_MouseDown(MainWindow MainWind, string Tag)
{
Point Pos = new Point();
Pos = System.Windows.Input.Mouse.GetPosition(MainWind.Main_Canvas);
if (Drag == false)
{
Drag = true;
Gate_list.Add(new Gate_Class(Convert.ToInt32(Tag),new Rectangle()));
MainWind.Main_Canvas.Children.Add(Gate_list[Gate_list.Count-1].Get_Rect());
System.Windows.Controls.Canvas.SetLeft(Gate_list[Gate_list.Count - 1], Pos.X);
}
}
}
I believe there should be an away to transfer system.windows but I don't know.
My question comes down to finding a way to convert Gate_list[] to UIElement.
Gate_Class is apparently not a UIElement. It should have a Rectangle property that returns the Rectangle that you pass to its constructor. You can then set the Canvas.Left property of the Rectangle:
System.Windows.Controls.Canvas.SetLeft(Gate_list[Gate_list.Count - 1].Rectangle, Pos.X);
public class Gate_Class
{
public Gate_Class(int tag, Rectangle rectangle)
{
//...
Rectangle = rectangle;
}
public Rectangle Rectangle { get; }
}
I think in last line of your code you are providing Gate_list[Gate_list.Count - 1] but this is your custom class and not the Rectangle you just created. I can deduce from your code that you can get the Rectangle by using Gate_list[Gate_list.Count - 1].Get_Rect().
In other words it seems to me that you need to add .Get_Rect() to actually get the rectangle.
If this doesn't work, please provide code of your Gate_Class and the error message you are getting.
I instantiate new Ui elements onto a canvas like so:
public class MainForm :Canvas
{
List<BannerImage> bannerList;
AddImages()
{
bannerImage = new BannerImage("title", "content");
//accompanied with animation
Children.Add(bannerImage);
bannerList.Add(bannerImage);
}
I need to call the bannerImages to get their current position, the following works:
foreach(bannerItem in bannerList)
{
double rightPosition = Canvas.GetRight(bannerItem);
}
But I can't do the following:
bannerItem.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)
Size s = bannerItem.DesiredSize;
Which always ends up to be
{0,0}
Why is it that I can get the position of the item on the canvas but not the size?
I am just going to take a guess that you didn't override MeasureOverride. I will provide a basic implementation assuming that each element is stacked, but you would need to modify it to take into consideration your child controls and what ever custom layout you may have created (I don't know if they are in a grid, horizontally stacked, in a some kind of scrolled container, etc).
protected override Size MeasureOverride(Size availableSize)
{
var height = 0.0;
var width = 0.0;
foreach (UIElement child in InternalChildren)
{
child.Measure(availableSize);
if (child.DesiredSize.Width > width) width = child.DesiredSize.Width;
height += child.DesiredSize.Height;
}
width = double.IsPositiveInfinity(availableSize.Width) ? width : Math.Min(width, availableSize.Width);
height = double.IsPositiveInfinity(availableSize.Height) ? height : Math.Min(height, availableSize.Height);
return new Size(width, height);
}
Edit
I realized that I explained the issue in a comment, but didn't add it into my answer. The reason you can't get the size is because you have to provide an override in your derived class to compute it. By default, Canvas returns a DesiredSize of 0 since it will adapt to whatever size is assigned to it. In the case of your derived control, you have a Canvas as the base class but you have added additional controls to it. If you don't provide an override of the MeasureOverride method, then the base one (the one implemented by Canvas) is the only one that is called. The base Canvas knows nothing of your controls size requirements. You probably also will need to override ArrangeOverride. This article provides a pretty good explanation about the two methods, what they do and why you need to override them. It also provides and example of both methods.
Following an example from the Book "WPF Control Development Unleashed", i modified a ProgressBar to show a circular timer instead. This contains an Arc which runs counterclockwise around the center of the window (depending on the value of my ProgressBar).
My Arc-class inherits from System.Windows.Shapes.Shape and I use the RenderSize.Width and RenderSize.Height Properties to scale it depending on my Windowsize.
This seemed to work fine at first, but the Rendersize only seems to increase. Thus my arc scales up perfectly fine when i extend the window which contains the ProgressBar, but when i size it down again the size of the arc doesn't decrease.
My ControlTemplate contains a grid in which all the elements (including the arc) are set up, so all other Elements that don't depend directly on the RenderSize (some ellipses and a textblock) scale up and down as desired.
Do you have any ideas why the RenderSize behaves like described and which values i could use instead to calculate the x/y coordinates of start/endpoints of the arc?
Remark: if i set width and height of the grid to a fixed value, the rendersize does not change at all which confuses me even more, since the ellipses and textbox scale accordingly to the grid already.
I also read the book "WPF Control Development Unleashed". And see the scale issue of clock control. The problem is RenderSize was not changed when the window changed its size. My solution is add some logic to ArrangeOverride and MessureOverride method.
private Size _finalSize;
protected override Size MeasureOverride(Size availableSize)
{
Debug.WriteLine(string.Format("Mesure:{0},{1}", availableSize.Width, availableSize.Height));
Size desiredSize = base.MeasureOverride(availableSize);
_finalSize = availableSize;
return desiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
Debug.WriteLine(string.Format("Arrange:{0},{1}", _finalSize.Width, _finalSize.Height));
base.ArrangeOverride(_finalSize);
return _finalSize;
}
=========================================================================
I read some materials.They said Messure and Arrange can have same logic but they should not share the data. Luckily I find another solution. First, the control should have the stretch dependency property. And then change the ArcGeometry method.
static Arc()
{
StretchProperty.OverrideMetadata(typeof(Arc), new FrameworkPropertyMetadata(Stretch.Fill));
}
private Geometry GetArcGeometry2()
{
Point startPoint = PointAtAngle(RenderSize, Math.Min(StartAngle, EndAngle));
Point endPoint = PointAtAngle(RenderSize, Math.Max(StartAngle, EndAngle));
Point stopPoint = PointAtAngle(RenderSize, Math.Max(EndAngle, 359.99));//Add this
Size arcSize = new Size(Math.Max(0, (RenderSize.Width - StrokeThickness)/2),
Math.Max(0, (RenderSize.Height - StrokeThickness)/2));
bool isLargeArc = Math.Abs(EndAngle - StartAngle) > 180;
StreamGeometry geom = new StreamGeometry();
using (StreamGeometryContext context = geom.Open())
{
context.BeginFigure(startPoint, false, false);
context.ArcTo(endPoint, arcSize, 0, isLargeArc, SweepDirection.Counterclockwise, true, false);
context.ArcTo(stopPoint, arcSize, 0, !isLargeArc, SweepDirection.Counterclockwise, false, false);//add blank arc
}
geom.Transform = new TranslateTransform(StrokeThickness/2, StrokeThickness/2);
return geom;
}
I had a similar issue, and (after looking at wwg2222's answer) solved it by adding the following:
protected override Size MeasureOverride(Size constraint) =>
new Size(constraint.Width, constraint.Height);
After this point, the shape sized properly and the DefiningGeometry override had the proper RenderSize to work with. I didn't need to override the ArrangeOverride function.
I made a round progressbar myself, but i didn't use the RenderSize at all.
A better approach is to define an InnerRadius and OuterRadius property on your Arc and let their values be between 0 and 1.
Then, do the calculations in the Arc's DefiningGeometry override.
Since the Arc is a Shape, you can use its Stretch property to scale it up inside the controltemplate.
One issue with scaling is that the arc's bounding box varies with its value/angle. This is why you need to define an EllipseGeometry inside DefiningGeometry to fixate the boundaries of the Arc. And because of this extra Geometry I even added a second EllipseGeometry to get the filling right (because of the FillRule.EvenOdd value).
Good luck!
I want to make a panel have a thick border. Can I set this somehow?
PS, I am using C#. VS 2008.
Jim,
I've made a user control and given is a ParentControlDesigner. As I indicated in my comment it's not a perfect solution to what you're asking for. But it should be a good starting point. Oh any FYI, I've got it with a customizable border color too. I was inspired by another SO post to pursue this... It was trickier than I expected.
To get things to rearrange correctly when setting the border size a call to PerformLayout is made. The override to DisplayRectangle and the call to SetDisplayRectLocation in OnResize cause the proper repositioning of the child controls. As well the child controls don't have the expected "0,0" when in the upper left most... unless border width is set to 0... And OnPaint provides the custom drawing of the border.
Best of luck to ya! Making custom controls that are parents is tricky, but not impossible.
[Designer(typeof(ParentControlDesigner))]
public partial class CustomPanel : UserControl
{
Color _borderColor = Color.Blue;
int _borderWidth = 5;
public int BorderWidth
{
get { return _borderWidth; }
set { _borderWidth = value;
Invalidate();
PerformLayout();
}
}
public CustomPanel() { InitializeComponent(); }
public override Rectangle DisplayRectangle
{
get
{
return new Rectangle(_borderWidth, _borderWidth, Bounds.Width - _borderWidth * 2, Bounds.Height - _borderWidth * 2);
}
}
public Color BorderColor
{
get { return _borderColor; }
set { _borderColor = value; Invalidate(); }
}
new public BorderStyle BorderStyle
{
get { return _borderWidth == 0 ? BorderStyle.None : BorderStyle.FixedSingle; }
set { }
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaintBackground(e);
if (this.BorderStyle == BorderStyle.FixedSingle)
{
using (Pen p = new Pen(_borderColor, _borderWidth))
{
Rectangle r = ClientRectangle;
// now for the funky stuff...
// to get the rectangle drawn correctly, we actually need to
// adjust the rectangle as .net centers the line, based on width,
// on the provided rectangle.
r.Inflate(-Convert.ToInt32(_borderWidth / 2.0 + .5), -Convert.ToInt32(_borderWidth / 2.0 + .5));
e.Graphics.DrawRectangle(p, r);
}
}
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
SetDisplayRectLocation(_borderWidth, _borderWidth);
}
}
Just implement the panel's Paint event and draw a border. For example:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
panel1.Paint += panel1_Paint;
}
VisualStyleRenderer renderer = new VisualStyleRenderer(VisualStyleElement.Button.PushButton.Normal);
private void panel1_Paint(object sender, PaintEventArgs e) {
renderer.DrawEdge(e.Graphics, panel1.ClientRectangle,
Edges.Bottom | Edges.Left | Edges.Right | Edges.Top,
EdgeStyle.Raised, EdgeEffects.Flat);
}
}
}
Play around with the arguments to find something you like. You ought to add code to fallback to ControlPaint.DrawBorder if visual styles aren't enabled. Meh.
If this is just about presentation, put a panel that fills the form with a background color of the border color you want and a Dock style of Fill. Place another panel inside this one with the standard background color and a Dock style of Fill. Play with Padding and Margin of the two panels to get the border size you wish (I forget which param applies correclty to the inner panel and the outer panel). Place your controls on the interior panel. With both panels set to Dock=Fill, form resizing is automatically handled for you. You may need to experiment with some of the controls, but I have done this many times with no issues for both application main windows and popup forms.
This is an old post but I still find it useful. And I just found another way.
ControlPaint.DrawBorder(e.Graphics, control.ClientRectangle,
Color.Black, BORDER_SIZE, ButtonBorderStyle.Inset,
Color.Black, BORDER_SIZE, ButtonBorderStyle.Inset,
Color.Black, BORDER_SIZE, ButtonBorderStyle.Inset,
Color.Black, BORDER_SIZE, ButtonBorderStyle.Inset);
This is kind of rigging it but I've always just used a label for each side border. You'll have to set the autosize property to false and dock one to each side (left, right, top, bottom). Then just set the width/height/background color to do what you want.
You could easily make this a user control and just expose some custom public properties to set the width/height for you and the background color of all the labels to change the color.
I am trying to create a series of UserControls that all inherit from a custom UserControl object. One of the key behaviors I want to implement is the ability to dynamically resize all of the controls to fit the control size.
In order to do this, I need to get the initial width and height of the control to compare it to the resized dimensions. In my inherited controls, I can put code in the constructor after the InitializeComponent() call to grab the dimensions. Is there any way I can do this from the base object code?
Also, if there is a better approach to doing this, I am open to suggestions.
I ended up using my base control's dimensions as the fixed size for all inherited controls. This was fairly easy to do by overriding the SetBoundsCore() method:
public partial class BaseControl : UserControl
{
private int _defaultWidth;
private int _defaultHeight;
public BaseControl()
{
InitializeComponent();
_defaultWidth = this.Width;
_defaultHeight = this.Height;
}
protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
{
if (this.DesignMode)
{
width = _defaultWidth;
height = _defaultHeight;
}
base.SetBoundsCore(x, y, width, height, specified);
}
}
Any controls inherited BaseControl automatically default to its fixed dimensions.
At runtime, my resize code calculates a resize ratio based on the new Width and Height vs. the _defaultWidth and _defaultHeight members.
In the user control, take advantage of the docking and anchoring properties of every control in the container. When the user control is sized or resized, the contents should adjust themselves automatically. Then in code, all you need to do is set the size of the user control.