I'm trying to create a UILabel with padding in my Xamarin.iOS app. The most popular solution in native Objective-C apps is overriding drawTextInRect:
- (void)drawTextInRect:(CGRect)rect {
UIEdgeInsets insets = {0, 5, 0, 5};
return [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
}
As simple as this seems, I can't quite figure out how to translate it to C#. Here's my best stab at it:
internal class PaddedLabel : UILabel
{
public UIEdgeInsets Insets { get; set; }
public override void DrawText(RectangleF rect)
{
var padded = new RectangleF(rect.X + Insets.Left, rect.Y, rext.Width + Insets.Left + Insets.Right, rect.Height);
base.DrawText(padded);
}
}
This does seem to move the label's text, but it doesn't resize the label.
I think the main issue is that I can't find the Xamarin equivalent of UIEdgeInsetsInsetRect.
Any suggestions?
The C# equivalent of the ObjC function UIEdgeInsetsInsetRect is a instance method of UIEdgeInsets named InsetRect and it's not identical to your RectangleF calculations (which is likely your problem).
To use it you can do:
public override void DrawText(RectangleF rect)
{
base.DrawText (Insets.InsetRect (rect));
}
You have to override both DrawText and TextRectForBounds.
If you don't override TextRectForBounds, the text will be clipped.
Actually, you override this method to compensate the space which is occupied by padding and ask iOS to draw the text in a bigger rectangle.
public partial class LabelWithBorder
: UILabel
{
private UIEdgeInsets EdgeInsets = new UIEdgeInsets(5, 5, 5, 5);
private UIEdgeInsets InverseEdgeInsets = new UIEdgeInsets(-5, -5, -5, -5);
public LabelWithBorder(IntPtr handle) : base(handle)
{
}
public override CoreGraphics.CGRect TextRectForBounds(CoreGraphics.CGRect bounds, nint numberOfLines)
{
var textRect = base.TextRectForBounds(EdgeInsets.InsetRect(bounds), numberOfLines);
return InverseEdgeInsets.InsetRect(textRect);
}
public override void DrawText(CoreGraphics.CGRect rect)
{
base.DrawText(EdgeInsets.InsetRect(rect));
}
}
Rather than overriding DrawText() in the subclass of UILabel, override it's intrinsic content size. This way auto-layout takes the padding into consideration. For example here's my derived class of UILabel:
public class PaddedLabel : UILabel
{
private readonly float _top;
private readonly float _left;
private readonly float _right;
private readonly float _bottom;
public PaddedLabel(float top, float left, float right, float bottom)
{
_top = top;
_left = left;
_right = right;
_bottom = bottom;
}
public override CGSize IntrinsicContentSize => new CGSize(
base.IntrinsicContentSize.Width + _left + _right,
base.IntrinsicContentSize.Height + _top + _bottom
);
}
I have created a generic Padding UIView class that wraps any IOS UI element that is derived from UIView.
Basically it nests the desired UIView into another view and takes care of all the padding work.
usage:
var myPaddedView = new PaddedUIView<UILabel>();
myPaddedView.Frame = TheActualFrame;
myPaddedView.Padding = 15f
myPaddedView.NestedView.Text = "Hello padded world"; // all the label Properties are available without side effects
Here is the class:
public class PaddedUIView<T>: UIView where T : UIView, new()
{
private float _padding;
private T _nestedView;
public PaddedUIView()
{
Initialize();
}
public PaddedUIView(RectangleF bounds)
: base(bounds)
{
Initialize();
}
void Initialize()
{
if(_nestedView == null)
{
_nestedView = new T();
this.AddSubview(_nestedView);
}
_nestedView.Frame = new RectangleF(_padding,_padding,Frame.Width - 2 * _padding, Frame.Height - 2 * _padding);
}
public T NestedView
{
get { return _nestedView; }
}
public float Padding
{
get { return _padding; }
set { if(value != _padding) { _padding = value; Initialize(); }}
}
public override RectangleF Frame
{
get { return base.Frame; }
set { base.Frame = value; Initialize(); }
}
}
The correct way to do this seems to have slightly changed since the original answer was given, and it took me awhile to figure out the new correct syntax. Here it is for anyone else who stumbles across this. This code would put padding of 5 on both the left and right sides of the view. This is in Xamarin.iOS
public override void DrawText(CGRect rect)
{
rect.X = 5;
rect.Width = rect.Width - 10; // or whatever padding settings you want
base.DrawText(AlignmentRectInsets.InsetRect(rect));
}
poupou's solution marked as the best answer doesn't work. I found another way to add padding to label.
public class PaddedLabel : UILabel
{
public PaddedLabel(IntPtr intPtr) : base(intPtr)
{
TextAlignment = UITextAlignment.Center;
}
public override CGSize IntrinsicContentSize
{
get
{
var size = base.IntrinsicContentSize;
return new CGSize(size + 16, size);
}
}
}
Related
I'm learning inheritance and I understand the code below.
namespace InheritanceApplication {
class Shape {
public void setWidth(int w) {
width = w;
}
public void setHeight(int h) {
height = h;
}
protected int width;
protected int height;
}
// Base class PaintCost
public interface PaintCost {
int getCost(int area);
}
// Derived class
class Rectangle : Shape, PaintCost {
public int getArea() {
return (width * height);
}
public int getCost(int area) {
return area * 70;
}
}
class RectangleTester {
static void Main(string[] args) {
Rectangle Rect = new Rectangle();
int area;
Rect.setWidth(5);
Rect.setHeight(7);
area = Rect.getArea();
// Print the area of the object.
Console.WriteLine("Total area: {0}", Rect.getArea());
Console.WriteLine("Total paint cost: ${0}" , Rect.getCost(area));
Console.ReadKey();
}
}
}
However, why have they created the set height and set width functions. Would it not be better practice to simply just do this:
public int width {get;set;}
public int height {get;set;}
and then in the main class just do something like below:
rect.width = 5;
rect.height = 7;
Many thanks,
Amir
I'm sure others will provide different points, but here are my main 2 reasons for using gets/sets. If these don't apply for a given property, chances are I won't use getters/setters.
1 - Debugging
It makes it significantly easier to debug data propagation (how data gets passed around) if you can debug a setter that you're concerned about. You can easily throw in a Debug.Print call and debug the value being set if you're concerned it's being passed the wrong value. Or you could place break points and actually debug through the stack trace. For example:
class Shape {
public void setWidth(int w) {
if(w < 0)
Debug.Print("width is less than 0!");
width = w;
}
public void setHeight(int h) {
height = h;
}
protected int width;
protected int height;
}
2 - Value Change Actions
There may be better ways to achieve this, but I like being able to add simple logic to setters to ensure that any logic that needs to run when a value changes does so. For instance I may use the following:
public void SetWindowHeight(int newHeight)
{
if(WindowHeight == newHeight)
return;
WindowHeight = newHeight;
UpdateWindowDisplay();
}
public int GetWindowHeight()
{
return WindowHeight;
}
private int WindowHeight;
public void UpdateWindowDisplay()
{
Window.UpdateHeight(WindowHeight);
// Other window display logic
}
Although personally I prefer to use property gets/sets, but that's just my preference.
public int WindowHeight
{
get
{
return windowHeight;
}
set
{
if(windowHeight == value)
return;
windowHeight = value;
UpdateWindowDisplay();
}
}
private int windowHeight;
public void UpdateWindowDisplay()
{
Window.UpdateHeight(WindowHeight);
// Other window display logic
}
What is the to do?
to save a specific edge property to square use the rectangle property?
public Square(double edge) : base(edge, edge)
{
}
OR
public Square(double edge) : base(edge, edge)
{
Edge = edge;
}
public Rectangle(double width, double height)
{
Width = width;
Height = height;
}
It does not really matter whether you add a new Edge property in Square, but there is something more important than this. And that is consistency.
If your classes are immutable, then good. But if your classes are mutable, you need to be consistent with the three (or two if you decided not to add Edge) properties in Square. When width changes, height should also change. When you change Edge, both Width and Height should change.
class Square : Rectangle {
public override double Width {
get { return base.Width; }
set {
base.Width = value;
base.Height = value;
}
}
public override double Height {
get { return base.Height; }
set {
base.Width = value;
base.Height = value;
}
}
public double Edge {
get { return Width; }
set {
base.Width = value;
base.Height = value;
}
}
public Square(double edge) : base(edge, edge) {
}
}
Note that the constructor is now empty, because Edge isn't really "stored". When you access it, it just returns the width.
P.S. I don't think Edge is a good name. I would call it SideLength.
The shape is not drawing correctly or not at all - that is, if I click towards the top left of the canvas, it will draw an ellipse but not anywhere else like this. Is the code logically correct? Everything seems good but perhaps I am missing something.
/* in mainwindow.xaml */
private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (btnNode.IsChecked == true)
{
CreateNode(e.GetPosition(Canvas));
}
}
private void CreateNode(Point origin)
{
Node n = new Node("new_" + cmbStart.Items.Count, origin) { Fill = new SolidColorBrush(Colors.Tomato), Width = 60, Height = 60 };
Canvas.Children.Add(n);
Canvas.SetLeft(n, origin.X - n.Width/2);
Canvas.SetTop(n, origin.Y - n.Height/2);
}
/* in Node class */
private void Init(Point p)
{
X = p.X;
Y = p.Y;
}
public double Y
{
get { return (double)this.GetValue(YProperty); }
set { this.SetValue(YProperty, value); }
}
public double X
{
get { return (double) this.GetValue(XProperty); }
set { this.SetValue(XProperty, value); }
}
public static readonly DependencyProperty XProperty = DependencyProperty.Register("X", typeof(double), typeof(Node), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty YProperty = DependencyProperty.Register("Y", typeof(double), typeof(Node), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));
protected override Geometry DefiningGeometry
{
get
{
Console.WriteLine("rendering at: " + this.X + ", " + this.Y + "---Scale: " + Width);
return new EllipseGeometry(new Point(this.X, this.Y), Width, Height);
}
}
The EllipseGeometry constructor has the radiusX and radiusY parameters to pass radii, not diameters.
If you want to draw a full ellipse into the bounds of your custom control, you should use Width/2 and Height/2 as parameter values:
protected override Geometry DefiningGeometry
{
get
{
return new EllipseGeometry(new Point(X, Y), Width / 2, Height / 2);
}
}
I'm learning to use the as operator, and my goal was to create an option window (non windows form) that can:
Have options added to it (for flexibility, in case I want to use if statements to add menu items)
Be able to display text, textures, or a class (using the classes draw function)
Be controlled through the host GameState
I still haven't added the options for indicating an item is selected, my apologies for not posting a complete work. I also have not sorted the code into regions yet. Sorry again!
Is my code (particularly the draw function) properly using the is and as operators properly, from a performance and readability (non spaghetti code) standpoint?
public class OptionWindow : DrawableGameComponent
{
public Dictionary<int, Option> options;
int selectedOption;
bool windowLoops;
Rectangle drawRectangle;
int spacer;
int totalItemHeight;
SpriteFont sf;
SpriteBatch sb;
public Rectangle DrawRectangle
{
get { return drawRectangle; }
set { drawRectangle = value; }
}
public int SelectedOption
{
get { return selectedOption; }
set
{
if (windowLoops)
{
if (selectedOption >= options.Count())
selectedOption = 0;
if (selectedOption < 0)
selectedOption = options.Count() - 1;
}
else
{
if (selectedOption >= options.Count())
selectedOption = options.Count() - 1;
if (selectedOption < 0)
selectedOption = 0;
}
}
}
public OptionWindow(Game game, bool windowLoops, SpriteFont sf, Rectangle drawRectangle)
: base(game)
{
options = new Dictionary<int, Option>();
this.windowLoops = windowLoops;
this.sf = sf;
DrawRectangle = new Rectangle(drawRectangle.X, drawRectangle.Y, drawRectangle.Width, drawRectangle.Height);
}
public void Add(object option, bool selectable, bool defaultSelection, int height)
{
options.Add(options.Count(), new Option(selectable, option, height));
if (defaultSelection)
SelectedOption = options.Count() - 1;
UpdatePositions();
}
public void UpdatePositions()
{
UpdateTotalItemHeight();
if (options.Count() - 1 != 0)
spacer = (drawRectangle.Height - totalItemHeight) / (options.Count() - 1);
for (int i = 0; i < options.Count(); i++)
{
if (i == 0)
options[i].Position = new Vector2(drawRectangle.X, drawRectangle.Y);
else
{
options[i].Position = new Vector2(
drawRectangle.X,
options[i - 1].Position.Y + options[i - 1].Height + spacer);
}
}
}
public void UpdateTotalItemHeight()
{
totalItemHeight = 0;
for (int i = 0; i < options.Count(); i++)
{
totalItemHeight += options[i].Height;
}
}
protected override void LoadContent()
{
sb = new SpriteBatch(GraphicsDevice);
base.LoadContent();
}
public override void Draw(GameTime gameTime)
{
for (int i = 0; i < options.Count(); i++)
{
if (options[i].OptionObject is string)
sb.DrawString(sf, options[i].OptionObject as string, options[i].Position, Color.White);
if (options[i].OptionObject is Texture2D)
sb.Draw(options[i].OptionObject as Texture2D,
new Rectangle(
(int)options[i].Position.X,
(int)options[i].Position.Y,
options[i].Height,
(options[i].Height / (options[i].OptionObject as Texture2D).Height) * (options[i].OptionObject as Texture2D).Width),
Color.White);
if (options[i].OptionObject is DisplayObject)
(options[i].OptionObject as DisplayObject).Draw(gameTime);
}
base.Draw(gameTime);
}
}
public class Option
{
bool selectable;
object optionObject;
int height;
Vector2 position;
public bool Selectable
{
get { return selectable; }
set { selectable = value; }
}
public object OptionObject
{
get { return optionObject; }
set { optionObject = value; }
}
public int Height
{
get { return height; }
set { height = value; }
}
public Vector2 Position
{
get { return position; }
set { position = value; }
}
public Option(bool selectable, object option, int height)
{
Selectable = selectable;
OptionObject = option;
Height = height;
}
}
It is never adviseable to use is and then as. The usual way to go would be to either of the following:
just use is (if you just want to know the type without subsequent casting)
assign the result of as to a variable and check whether that variable is (not) null
The code analysis tool FxCop helps you find any spots in your code that use is and then as and warns you because of performance concerns.
Note however that a better approach altogether might be to declare your OptionObject property as some abstract class with a Draw method. You could then derive a subclass for strings, one for Texture2D instances and another one for DisplayObject instances and just call Draw in your OptionWindow.Draw method. This would leave the decision which actual drawing operations to execute up to built-in polymorphism features of the framework.
By way of an intro, I'm creating a basic Quadtree engine for personal learning purposes. I'm wanting this engine to have the capability of working with many different types of shapes (at the moment I'm going with circles and squares) that will all move around in a window and perform some sort of action when collision occurs.
After asking a question on the topic of generic lists earlier, I have decided on using an interface for polymorphism. The best interface for this would be an interface utilising Vector2 due to the fact that every object that appears in my Quadtree will have an x,y position and Vector2 covers that nicely. Here is my code as it currently stands:
public interface ISpatialNode {
Vector2 position { get; set; }
}
public class QShape {
public string colour { get; set; }
}
public class QCircle : QShape, ISpatialNode {
public int radius;
public Vector2 position {
get { return position; }
set { position = value; }
}
public QCircle(int theRadius, float theX, float theY, string theColour) {
this.radius = theRadius;
this.position = new Vector2(theX, theY);
this.colour = theColour;
}
}
public class QSquare : QShape, ISpatialNode {
public int sideLength;
public Vector2 position {
get { return position; }
set { position = value; }
}
public QSquare(int theSideLength, float theX, float theY, string theColour) {
this.sideLength = theSideLength;
this.position = new Vector2(theX, theY);
this.colour = theColour;
}
}
So I'll be eventually wanting to have an interface that works to the point that I can use the generic list List<ISpatialNode> QObjectList = new List<ISpatialNode>(); and I can add shapes to it using the code QObjectList.Add(new QCircle(50, 400, 300, "Red")); or QObjectList.Add(new QSquare(100, 400, 300, "Blue")); or something along those lines (keep in mind that I'll be wanting to add different shapes later along the line).
Problem is, this code doesn't seem to work when I call it from here (Initialize() is the XNA method):
protected override void Initialize() {
QObjectList.Add(new QCircle(5, 10, 10, "Red"));
base.Initialize();
}
So my question has two parts:
1. Why does this code give me a stackoverflow error at the set {
position = value; } part of my QCircle and QSquare classes?
2. Would this be an efficient/effective way of utilising interfaces for
polymorphism?
The problem is in your property it is setting itself in circular loop
public Vector2 position { get ; set ; }
Or declare a private field
private Vector2 _position;
public Vector2 position {
get { return _position; }
set { _position = value; }
}
Stack overflow is because:
public Vector2 position {
get { return position; }
set { position = value; }
}
the set actually sets the same again. You may want this:
private Vector2 _position;
public Vector2 position {
get { return _position; }
set { _position = value; }
}
or its short version:
public Vector2 position { get; set; } //BTW, the c# standard is to use upper camel case property names
Regarding the use of polymorphism, it seems right in this scenario.