I am trying to write something along of a "clean" code... I want to make a Pong game, for now based on Forms.
I want to divide the game nicely into classes.
I want to have a ball class, AI that inherits from player class, I want to use the premade Form class for setting main Form properties (width etc).
I made a player class as such and I would like to ask you if the approach for naming, getters and setters and the general idea is correct. Isn't certain bits (if not all of it) rather redundant or badly written, I do not want to base entire "project" on bad assumptions and multiply the same mistakes all over the code.
namespace Pong
{
public class Player
{
protected PictureBox PaddleBox { get; set; }
protected Size PlayerSize
{
get
{
return PlayerSize;
}
set
{
if (PlayerSize.Height > 0 && PlayerSize.Width > 0)
{
PlayerSize = new Size(value.Width, value.Height);
PaddleBox.Size = PlayerSize;
}
}
}
protected Point Location
{
get
{
return Location;
}
set
{
PaddleBox.Location = new Point(value.X, value.Y);
}
}
protected Color BackColor
{
get
{
return BackColor;
}
set
{
PaddleBox.BackColor = value;
}
}
public Player()
{
PaddleBox = new PictureBox();
}
}
}
FORM class looks something along of this for now, maybe I should pass parameters such as size,location and color in the constructor? What is the best?
namespace Pong
{
public partial class Form1 : Form
{
public Timer gameTime;
const int screenWidth = 1248;
const int screenHeight = 720;
public Form1()
{
InitializeComponent();
this.Height= screenHeight;
this.Width=screenWidth;
this.StartPosition=FormStartPosition.CenterScreen;
Player player = new Player();
player.PaddleBox.Size = new Size(20, 50);
player.PaddleBox.Location = new Point(player.PaddleBox.Width / 2, ClientSize.Height/2-player.PaddleBox.Height/2);
player.PaddleBox.BackColor = Color.Blue;
this.Controls.Add(player.PaddleBox);
gameTime = new Timer();
gameTime.Enabled = true;
}
void gameTime_Tick(object sender, EventArgs e)
{
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
}
You got an issue here:
protected Point Location
{
get
{
return Location; // <--- this is a circular reference..
// meaning, it will recall this getter again.
}
set
{
PaddleBox.Location = new Point(value.X, value.Y);
}
}
use this instead:
protected Point Location
{
get
{
return PaddleBox.Location;
}
set
{
PaddleBox.Location = value;
}
}
Same with protected Color BackColor
Here is an example, how I would implement it (in your current programming style (powered by notepad))
namespace Pong
{
public partial class Form1 : Form
{
public Timer gameTime;
const int screenWidth = 1248;
const int screenHeight = 720;
public Form1()
{
InitializeComponent();
this.Height= screenHeight;
this.Width=screenWidth;
this.StartPosition=FormStartPosition.CenterScreen;
Player player = new Player(this);
player.PlayerSize = new Size(20, 50);
player.Location = new Point(player.PaddleBox.Width / 2, ClientSize.Height/2-player.PaddleBox.Height/2); // <-- the location is always the upperleft point. don't do this...
player.BackColor = Color.Blue;
gameTime = new Timer();
gameTime.Enabled = true;
}
private void gameTime_Tick(object sender, EventArgs e)
{
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
public class Player
{
private PictureBox _paddleBox;
protected Size PlayerSize
{
get
{
return _paddleBox.Size;
}
set
{
if (PlayerSize.Height == 0 || PlayerSize.Width == 0)
throw new ArgumentException("Size must be greater than 0");
_paddleBox.Size = value;
}
}
protected Point Location
{
get { return PaddleBox.Location; }
set { PaddleBox.Location = value; }
}
protected Color BackColor
{
get { return PaddleBox.BackColor; }
set { PaddleBox.BackColor = value; }
}
public Player(Form form)
{
PaddleBox = new PictureBox();
form.Controls.Add(PaddleBox);
}
}
}
You should try to isolate the picturebox in your player class, this will separate the functionality of the form and the picturebox...
Related
I found on the internet how to create your own checkbox. Unfortunately, there is a problem with the fact that you have to click very slowly to react to a change, and when I click on the text, nothing changes. I presented the whole action in the film. Please help.
https://www.youtube.com/watch?v=s6xmVAoUVJ8
Here are the files: https://drive.google.com/file/d/10pj6DRjCvfQc8_s0rUufJpN1kdft2sml/view?usp=sharing
CheckBox.cs:
class NowyCheckbox : Control
{
#region Private members
public bool IsChecked = false;
private Label CheckBoxLabel;
private Rectangle CheckBoxRectangle;
private bool MouseOver = false;
#endregion
#region Public Members (in Attributes)
private Color CheckBoxCharColorValue = Color.FromArgb(0, 0, 0);
public Color CheckBoxCharColor
{
get
{
return CheckBoxCharColorValue;
}
set
{
if (CheckBoxCharColor != value)
{
CheckBoxCharColorValue = value;
Invalidate();
}
}
}
private Color CheckBoxCharHighlightColorValue = Color.FromArgb(0, 120, 215);
public Color CheckBoxCharHighlightColor
{
get
{
return CheckBoxCharHighlightColorValue;
}
set
{
if (CheckBoxCharHighlightColor != value)
{
CheckBoxCharHighlightColorValue = value;
Invalidate();
}
}
}
private Font CheckBoxCharFontValue;
public Font CheckBoxCharFont
{
get
{
return CheckBoxCharFontValue;
}
set
{
if (CheckBoxCharFont != value)
{
CheckBoxCharFontValue = value;
Invalidate();
}
}
}
private string CheckBoxCharValue = "!";
public string CheckBoxChar
{
get
{
return CheckBoxCharValue;
}
set
{
if (CheckBoxChar != value)
{
CheckBoxCharValue = value;
RefreshLabel();
}
}
}
private Font CheckBoxFontValue;
public Font CheckBoxFont
{
get
{
return CheckBoxFontValue;
}
set
{
if (CheckBoxFont != value)
{
CheckBoxFontValue = value;
RefreshLabel();
}
}
}
private string CheckBoxTextValue = "Nowy CheckBox";
public string CheckBoxText
{
get
{
return CheckBoxTextValue;
}
set
{
if (CheckBoxText != value)
{
CheckBoxTextValue = value;
RefreshLabel();
}
}
}
private int CheckBoxSizeValue = 12;
public int CheckBoxSize
{
get
{
return CheckBoxSizeValue;
}
set
{
if (CheckBoxSize != value)
{
CheckBoxSizeValue = value;
RefreshLabel();
Invalidate();
}
}
}
private int CheckBoxFrameStrengthValue = 1;
public int CheckBoxFrameStrength
{
get
{
return CheckBoxFrameStrengthValue;
}
set
{
if (CheckBoxFrameStrengthValue != value)
{
CheckBoxFrameStrengthValue = value;
Invalidate();
}
}
}
#region Public Member
private int CheckBoxCharOffsetXValue = 0;
public int CheckBoxCharOffsetX
{
get
{
return CheckBoxCharOffsetXValue;
}
set
{
if (CheckBoxCharOffsetX != value)
{
CheckBoxCharOffsetXValue = value;
Invalidate();
}
}
}
private int CheckBoxCharOffsetYValue = 0;
public int CheckBoxCharOffsetY
{
get
{
return CheckBoxCharOffsetYValue;
}
set
{
if (CheckBoxCharOffsetY != value)
{
CheckBoxCharOffsetYValue = value;
Invalidate();
}
}
}
private int CheckBoxOffsetXValue = 0;
public int CheckBoxOffsetX
{
get
{
return CheckBoxOffsetXValue;
}
set
{
if (CheckBoxOffsetX != value)
{
CheckBoxOffsetXValue = value;
RefreshLabel();
}
}
}
private int CheckBoxOffsetYValue = 0;
public int CheckBoxOffsetY
{
get
{
return CheckBoxOffsetYValue;
}
set
{
if (CheckBoxOffsetY != value)
{
CheckBoxOffsetYValue = value;
RefreshLabel();
}
}
}
#endregion
#region Public Colors
private Color CheckBoxFrameColorValue = Color.FromArgb(0, 0, 0);
public Color CheckBoxFrameColor
{
get
{
return CheckBoxFrameColorValue;
}
set
{
if (CheckBoxFrameColorValue != value)
{
CheckBoxFrameColorValue = value;
Invalidate();
}
}
}
private Color CheckBoxFrameHighlightColorValue = Color.FromArgb(0, 120, 250);
public Color CheckBoxFrameHighlightColor
{
get
{
return CheckBoxFrameHighlightColorValue;
}
set
{
if (CheckBoxFrameHighlightColorValue != value)
{
CheckBoxFrameHighlightColorValue = value;
Invalidate();
}
}
}
private Color CheckBoxBackColorValue = Color.FromArgb(255, 255, 255);
public Color CheckBoxBackColor
{
get
{
return CheckBoxBackColorValue;
}
set
{
if (CheckBoxBackColorValue != value)
{
CheckBoxBackColorValue = value;
Invalidate();
}
}
}
private Color CheckBoxBackHighlightColorValue = Color.FromArgb(255, 255, 255);
public Color CheckBoxBackHighlightColor
{
get
{
return CheckBoxBackHighlightColorValue;
}
set
{
if (CheckBoxBackHighlightColorValue != value)
{
CheckBoxBackHighlightColorValue = value;
Invalidate();
}
}
}
private Color CheckBoxForeHighlightColorValue = Color.FromArgb(0, 0, 0);
public Color CheckBoxForeHighlightColor
{
get
{
return CheckBoxForeHighlightColorValue;
}
set
{
if (CheckBoxForeHighlightColorValue != value)
{
CheckBoxForeHighlightColorValue = value;
RefreshLabel();
}
}
}
private Color CheckBoxForeColorValue = Color.FromArgb(0, 0, 0);
public Color CheckBoxForeColor
{
get
{
return CheckBoxForeColorValue;
}
set
{
if (CheckBoxForeColorValue != value)
{
CheckBoxForeColorValue = value;
RefreshLabel();
}
}
}
#endregion
#endregion
#region Constructor
public NowyCheckbox()
{
DoubleBuffered = true;
Size = new Size(112, 18);
CheckBoxFont = this.Font;
CheckBoxCharFont = this.Font;
var midHeight = 8 - Font.Height / 2;
CheckBoxLabel = new Label()
{
Font = CheckBoxFont,
Size = new Size(this.Width - 16, this.Height),
Location = new Point(16, midHeight),
Text = CheckBoxText
};
Controls.Add(CheckBoxLabel);
CreateMouseEvents();
}
#endregion
#region Create mouse events
private void CreateMouseEvents()
{
MouseEnter += (sender, e) =>
{
OnCustomMouseEnter(e);
};
MouseLeave += (sender, e) =>
{
OnCustomMouseLeave(e);
};
MouseDown += (sender, e) =>
{
OnCustomMouseDown(e);
};
CheckBoxLabel.MouseEnter += (sender, e) =>
{
OnCustomMouseEnter(e);
};
CheckBoxLabel.MouseLeave += (sender, e) =>
{
OnCustomMouseLeave(e);
};
CheckBoxLabel.MouseDown += (sender, e) =>
{
OnCustomMouseDown(e);
};
}
#endregion
#region Mouse Events
private void OnCustomMouseDown(EventArgs e)
{
IsChecked = !IsChecked;
Invalidate();
}
private void OnCustomMouseEnter(EventArgs e)
{
if (MouseOver == false)
{
MouseOver = true;
Invalidate();
RefreshLabel();
}
}
private void OnCustomMouseLeave(EventArgs e)
{
if (MouseOver == true)
{
MouseOver = false;
Invalidate();
RefreshLabel();
}
}
#endregion
#region Paint NowyCheckbox
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
PaintRectangle(e);
if (IsChecked == true)
{
PaintArrowChar(e);
}
}
#endregion
#region Paint NowyCheckboxRectangle
private void PaintRectangle(PaintEventArgs e)
{
var midHeight = Height / 2 - CheckBoxSize / 2;
CheckBoxRectangle = new Rectangle(0, midHeight, CheckBoxSize, CheckBoxSize);
var fillColor = MouseOver == true ? CheckBoxBackHighlightColor : CheckBoxBackColor;
var frameColor = MouseOver == true ? CheckBoxFrameHighlightColor : CheckBoxFrameColor;
using (var pen = new Pen(frameColor, CheckBoxFrameStrength))
{
var brush = new SolidBrush(fillColor);
e.Graphics.FillRectangle(brush, CheckBoxRectangle);
e.Graphics.DrawRectangle(pen, CheckBoxRectangle);
}
}
#endregion
#region Paint Checkbox Arrow
private void PaintArrowChar(PaintEventArgs e)
{
var charColor = MouseOver == true ? CheckBoxCharHighlightColor : CheckBoxCharColor;
var midX = CheckBoxSize / 2 - 3 + CheckBoxCharOffsetX;
var midY = Height / 2 - CheckBoxCharFont.Height / 2 + CheckBoxCharOffsetY;
using (var brush = new SolidBrush(charColor))
{
e.Graphics.DrawString(CheckBoxChar, CheckBoxCharFont, brush, new Point(midX, midY));
}
}
#endregion
#region [OnResize]
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
RefreshLabel();
}
#endregion
#region Refresh Label
private void RefreshLabel()
{
if (CheckBoxLabel == null) return;
CheckBoxLabel.Font = CheckBoxFont;
CheckBoxLabel.Text = CheckBoxText;
CheckBoxLabel.ForeColor = MouseOver == true ? CheckBoxForeHighlightColor : CheckBoxForeColor;
var offsetWidth = Width - CheckBoxSize;
CheckBoxLabel.Size = new Size(offsetWidth, Height);
var offsetX = CheckBoxSize + 6 + CheckBoxOffsetX;
var midHeight = Height / 2 - CheckBoxFont.Height / 2 + CheckBoxOffsetY;
CheckBoxLabel.Location = new Point(offsetX, midHeight);
Invalidate();
}
#endregion
}
}
Form1.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Test
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void nowyCheckbox1_Click(object sender, EventArgs e)
{
if (nowyCheckbox1.IsChecked)
{
label2.Text = "Jestem włączony";
}
else
{
label2.Text = "Jestem wyłączony";
}
}
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
if (checkBox1.Checked)
{
label3.Text = "Jestem włączony";
}
else
{
label3.Text = "Jestem wyłączony";
}
}
}
An object like your custom check box, should have a mechanism used to notify a subscriber (like a Form) that something has happened. Notifications like mouse clicks, keystrokes, property changes...etc. In event-driven programming, your control should generate and publish an event to be received and handled by a subscriber through an Event Handler. Your custom control is missing that mechanism and this is the problem.
Let's apply that and create a simple check box control.
[DefaultProperty("Checked")]
[DefaultEvent("CheckedChanged")]
[DesignerCategory("Code")]
[ToolboxBitmap(typeof(CheckBox))]
public class NowyCheckBox : Control
{
Class members to keep:
The bounds of the check box and text areas.
The points of a check mark.
The current mouse state.
private enum MouseState : int
{
None,
Over,
Down
}
private Rectangle ChkRec, TxtRec;
private PointF[] ChkPoints;
private MouseState mouseState = MouseState.None;
In the constructor, set the shown below styles to reduce the flickering and to use the control in a container with a background image or gradient colors.
public NowyCheckBox() : base()
{
SetStyle(
ControlStyles.SupportsTransparentBackColor |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw, true);
UpdateStyles();
}
Add a method to create the drawing objects. Call it from the setters of the relevant properties
(such as CheckBoxSize) and methods (like OnSizeChanged) as shown next.
private void ResetDrawingObjects()
{
if (Width == 0 || Height == 0) return;
ChkRec = new Rectangle(new Point(0, (Height - checkBoxSize.Height) / 2), CheckBoxSize);
TxtRec = new Rectangle(ChkRec.Right + 5, 0, Width - ChkRec.Right - 10, Height);
if (this.RightToLeft == RightToLeft.Yes)
{
ChkRec.X = Width - 1 - ChkRec.Width;
TxtRec.X = ChkRec.X - 5 - TxtRec.Width;
}
var r = Rectangle.Inflate(ChkRec, -3, -3);
ChkPoints = new[]
{
new PointF(r.X, r.Y + (r.Height / 2f)),
new PointF(r.X + (r.Width / 2f) - 1, r.Bottom),
new PointF(r.Right, r.Y)
};
Invalidate();
}
Create a custom event and raise it whenever the Checked property changes.
public event EventHandler CheckedChanged;
protected virtual void OnCheckedChanged(EventArgs e) => CheckedChanged?.Invoke(this, e);
Add the properties.
private bool _checked;
[DefaultValue(false)]
public bool Checked
{
get => _checked;
set
{
if (_checked != value)
{
_checked = value;
Invalidate();
OnCheckedChanged(new EventArgs());
}
}
}
private Size checkBoxSize = new Size(12, 12);
[DefaultValue(typeof(Size), "12, 12")]
public Size CheckBoxSize
{
get => checkBoxSize;
set { checkBoxSize = value; ResetDrawingObjects(); }
}
// Add the rest...
Override the following methods to:
Update the mouseState variable and refresh the drawing by calling the Invalidate method if needed.
Call the ResetDrawingObjects method whenever the drawing objects need to resize and/or reposition.
Like the default CheckBox control, toggle the Checked property on Keys.Space press.
Draw the control.
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (mouseState != MouseState.Over)
{
mouseState = MouseState.Over;
// Pass ChkRec to update the checkbox area only.
Invalidate(ChkRec);
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (e.Button == MouseButtons.Left)
{
Focus();
mouseState = MouseState.Down;
Invalidate(ChkRec);
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
Checked = !Checked;
mouseState = MouseState.Over;
Invalidate(ChkRec);
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
mouseState = MouseState.None;
Invalidate(ChkRec);
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.KeyCode == Keys.Space) Checked = !Checked;
}
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
Invalidate();
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
ResetDrawingObjects();
}
protected override void OnRightToLeftChanged(EventArgs e)
{
base.OnRightToLeftChanged(e);
ResetDrawingObjects();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var g = e.Graphics;
// Get your colors to fill and draw...
var clr = mouseState == MouseState.Down
? // down color...
: mouseState == MouseState.Over
? // over color...
: // default color...;
using (var pn = new Pen(clr)) g.DrawRectangle(pn, ChkRec);
if (Checked)
{
clr = mouseState == MouseState.Down
? // down color...
: mouseState == MouseState.Over
? // over color...
: // default color...;
using (var pn = new Pen(clr, 2))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.DrawLines(pn, ChkPoints);
g.SmoothingMode = SmoothingMode.None;
}
/*
* In case you prefere drawing glyphs. Example:
using (var fnt = new Font("Marlett", 12))
{
var r = ChkRec;
r.Offset(2, -2);
TextRenderer.DrawText(g, "a", fnt, r, clr,
TextFormatFlags.HorizontalCenter |
TextFormatFlags.VerticalCenter);
}
*/
}
var flags = TextFormatFlags.VerticalCenter | TextFormatFlags.EndEllipsis;
if (this.RightToLeft == RightToLeft.Yes)
flags |= TextFormatFlags.Right;
else
flags |= TextFormatFlags.Left;
TextRenderer.DrawText(g, Text, Font, TxtRec, ForeColor, flags);
}
}
Put it together, rebuild, drop an instance, double click on it to subscribe to the default event CheckedChanged, and handle it as you want.
private void nowyCheckBox1_CheckedChanged(object sender, EventArgs e)
{
label2.Text = nowyCheckBox1.Checked
? "Jestem włączony"
: "Jestem wyłączony";
}
If you'd like to read more.
Handle and raise events
C# - Events
I am creating a button with a gradient in Xamarin forms. I am successfully making it but when i later on in the code try to update its color, nothing happens in the UI.
This is how the project is setup:
XAML:
<controls:FullyColoredGradient x:Name = "SelectedBackground" StartColor = "Purple" EndColor="Yellow" />
If i then later on the code do a void and try to update these colors like this:
SelectedBackground.EndColor = Color.Red;
SelectedBackground.StartColor = Color.Blue;
Then nothing happens. They do not recolor.
This is how my shared code looks:
public class FullyColoredGradient : Button
{
public static readonly BindableProperty StartColorProperty =
BindableProperty.Create(nameof(StartColor),
typeof(Color), typeof(FullyColoredGradient),
Color.Default);
public Color StartColor
{
get { return (Color)GetValue(StartColorProperty); }
set { SetValue(StartColorProperty, value); }
}
public static readonly BindableProperty EndColorProperty =
BindableProperty.Create(nameof(EndColor),
typeof(Color), typeof(FullyColoredGradient),
Color.Default);
public Color EndColor
{
get { return (Color)GetValue(EndColorProperty); }
set { SetValue(EndColorProperty, value); }
}
}
And this is my iOS renderer:
public class TransparentGradientColor_iOS : ButtonRenderer
{
CGRect rect;
CAGradientLayer gradientLayer;
public TransparentGradientColor_iOS() { }
public override void Draw(CGRect rect)
{
base.Draw(rect);
this.rect = rect;
FullyColoredGradient rcv = (FullyColoredGradient)Element;
if (rcv == null)
return;
this.ClipsToBounds = true;
this.Layer.MasksToBounds = true;
FullyColoredGradient stack = (FullyColoredGradient)this.Element;
CGColor startColor = stack.StartColor.ToCGColor();
CGColor endColor = stack.EndColor.ToCGColor();
#region for Vertical Gradient
this.gradientLayer = new CAGradientLayer()
{
StartPoint = new CGPoint(0, 0.5),
EndPoint = new CGPoint(1, 0.5)
};
#endregion
gradientLayer.Frame = rect;
gradientLayer.Colors = new CGColor[] { startColor, endColor };
NativeView.Layer.InsertSublayer(gradientLayer, 0);
}
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
UpdateColor();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
UpdateColor();
}
void UpdateColor()
{
if (gradientLayer != null)
{
FullyColoredGradient stack = (FullyColoredGradient)this.Element;
CGColor startColor = Xamarin.Forms.Color.White.ToCGColor();
CGColor endColor = Xamarin.Forms.Color.White.ToCGColor();
gradientLayer.Colors = new CGColor[] { startColor, endColor };
}
}
}
I have used this custom renderer, this will apply gradient to all buttons(you can create custom button if you want), you can try this out:
[assembly: ExportRenderer(typeof(Button), typeof(CustomButtonRendereriOS))]
namespace XYZ.iOS.Renderer
{
public class CustomButtonRendereriOS : ButtonRenderer
{
//To apply gradient background to button
public override CGRect Frame
{
get
{
return base.Frame;
}
set
{
if (value.Width > 0 && value.Height > 0)
{
foreach (var layer in Control?.Layer.Sublayers.Where(layer => layer is CAGradientLayer))
layer.Frame = new CGRect(0, 0, value.Width, value.Height);
}
base.Frame = value;
}
}
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
try
{
var gradient = new CAGradientLayer();
gradient.CornerRadius = Control.Layer.CornerRadius = 5;
gradient.Colors = new CGColor[]
{
UIColor.FromRGB(243, 112, 33).CGColor,
UIColor.FromRGB(226, 64, 64).CGColor
};
var layer = Control?.Layer.Sublayers.LastOrDefault();
Control?.Layer.InsertSublayerBelow(gradient, layer);
}
catch (Exception ex)
{
}
}
}
}
You can also use a gradient stack layout/ Frame as described here with help of custom renderer and use Tap Gesture of it for clicked event.
Gradient Button in Xamarin Forms
Hope this may solve your issue.
I am building a custom control that does some custom drawing based on some data. I want to update the drawing when arrange is called (i.e. the size is changed). But when I am changing my Children in ArrangeOverride() I get an infinite loop of course. How can I avoid this?
For simplicity it is easier for me to rebuild the whole visual tree instead of creating children once and resizing them individually.
Is there a better approach to do this? I can also live with just using a DrawingContext object and invoking my drawing logic there.
public class MyCanvas : Canvas
{
private static int _drawCounter = 0;
private System.Windows.Size _arrangeSize;
private MyData _data;
protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
{
_arrangeSize = arrangeSize;
Draw();
return base.ArrangeOverride(arrangeSize);
}
public void SetData(MyData data)
{
_data = data;
Draw();
}
private void Draw()
{
Children.Clear();
if (_data == null || _arrangeSize.IsEmpty)
{
return;
}
Children.Add(new TextBlock() {Text = (++_drawCounter).ToString()});
}
}
Here is how I solved it:
public class MyCanvas : Canvas
{
private readonly DispatcherTimer _dispatcherTimer;
private Size _arrangeSize;
private Size _drawnSize;
public MyCanvas()
{
_dispatcherTimer = new DispatcherTimer(DispatcherPriority.Render)
{
Interval = TimeSpan.FromMilliseconds(500)
};
_dispatcherTimer.Tick += (sender, args) =>
{
var dispatcherTimer = (DispatcherTimer)sender;
dispatcherTimer.Stop();
Debug.WriteLine("Draw call from DispatcherTimer");
Draw();
};
}
protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
{
_arrangeSize = arrangeSize;
if (_drawnSize != _arrangeSize)
{
QueueDrawCall();
}
return base.ArrangeOverride(arrangeSize);
}
private void QueueDrawCall()
{
if (_dispatcherTimer.IsEnabled)
{
_dispatcherTimer.Stop();
}
_dispatcherTimer.Start();
}
public void SetData(MyData data)
{
_data = data;
Console.WriteLine("Direct Draw Call " + data);
Draw();
}
private void Draw()
{
if (Children.Count > 0)
{
Children.Clear();
}
if (_data == null || _arrangeSize.IsEmpty)
{
return;
}
InternalDraw(); // Drawing logic goes in this function
_drawnSize = _arrangeSize;
}
}
I have a class for my sprites like so:
public class Sprite : System.ICloneable
{
public Image SpriteImage { get; private set; }
public Image SpriteMask { get; private set; }
public Sprite(Image img, Image maskImg = null)
{
this.SetImage(img, maskImg);
}
public void SetImage(Image img, Image maskImg = null)
{
this.SpriteImage = new Bitmap(img,img.Width,img.Height);
if (maskImg != null)
{
this.SpriteMask = new Bitmap(maskImg, maskImg.Width, maskImg.Height);
}
else
{
this.SpriteMask = this.SpriteImage;
}
}
public virtual object Clone()
{
return new Sprite(this.SpriteImage,this.SpriteMask);
}
public void FlipHorizontally()
{
this.SpriteImage.RotateFlip(RotateFlipType.RotateNoneFlipX);
this.SpriteMask.RotateFlip(RotateFlipType.RotateNoneFlipX);
}
public void FlipVertically()
{
this.SpriteImage.RotateFlip(RotateFlipType.RotateNoneFlipY);
this.SpriteMask.RotateFlip(RotateFlipType.RotateNoneFlipY);
}
}
since I have SpriteMask and SpriteImage I created a function to rotate them both without hassle. In another class I have:
public void ChangeDirection(bool facingRight)
{
if (facingRight != this.FacingRight)
{
for(int i = 0; i < this.Sprites.Count; i++)
{
this.Sprites[i].FlipHorizontally();
}
foreach (Weapon weapon in this.CollectedWeapons)
{
weapon.ChangeDirection(facingRight);
}
this.FacingRight = facingRight;
}
}
this.Sprites is List<Sprite> Sprites;. Now when this code is ran, the sprites to not flip, but if I change sprite.FlipHorizontally to this.Sprites[i].SpriteImage.RotateFlip(RotateFlipType.RotateNoneFlipX); it works fine. Why is this?
In your Sprite constructor you allow the sprite mask argument to be null. If it is null, look at what you do :
if (maskImg != null)
{
this.SpriteMask = new Bitmap(maskImg, maskImg.Width, maskImg.Height);
}
else
{
this.SpriteMask = this.SpriteImage;
}
You set SpriteMask to SpriteImage - since an image is a reference type you are only assigning the reference. There is still only one Image object but both variables are pointing to it. When you call :
public void FlipHorizontally()
{
this.SpriteImage.RotateFlip(RotateFlipType.RotateNoneFlipX);
this.SpriteMask.RotateFlip(RotateFlipType.RotateNoneFlipX);
}
...since SpriteImage and SpriteMask are the same object, you are flipping the image and then flipping it back again. What you might consider is making a copy of the sprite image for the mask if the mask is not provided :
if (maskImg != null)
{
this.SpriteMask = new Bitmap(maskImg, maskImg.Width, maskImg.Height);
}
else
{
this.SpriteMask = new Bitmap(this.SpriteImage);
}
I have a simple application. Here's how it works. I have a class (MyForm) that inherits from Windows.Forms. It has a button, a label and a textbox. It looks like a chat window.
There's another class (Cliente) that takes an array of strings and it returns a List with a MyForm instance for each element in the array.
I have a third class (Prueba) that makes use of the previous two classes to test them. This class creates four instances of MyForm, and displays them. (I will omit some code and functionality because I know it works correctly.)
I need to be able to type something in one window and when click on the button, it should broadcast this message and display it in all the other windows.
I know I have to use event handlers and delegates, but after hours of looking at tutorials everywhere I can't figure out what to put where.
Would you please help me? If you can point me to a good tutorial or example it'd be enough, but if you can be more specific on my code, it'd be great.
(I can't figure out how to make one instance of MyForm be aware of the other instances, who should be the listener here? I was thinking that Client, but I can't see how to do it.)
Any help will be appreciated!
//MyForm
namespace Dia26 {
//public delegate void ChangedEventHandler(object sender, EventArgs e);
public class MyForm : System.Windows.Forms.Form {
public Button btn = new Button();
public TextBox textbox = new TextBox();
public Label label = new Label();
public Button btnEnviar = new Button();
public delegate void OwnerChangedEventHandler(string newOwner); //~
public event OwnerChangedEventHandler OwnerChanged;
protected void btn_Click(object sender, System.EventArgs e) {
this.Close();
}
protected void btnEnviar_Click(object sender, System.EventArgs e) {
label.Text += textbox.Text + "\n";
textbox.Text = "";
if (this.OwnerChanged != null) {
this.OwnerChanged("something?");
}
}
public MyForm() {
btn.Text = "cerrar";
btn.Left = 400;
btn.Top = 280;
btn.Click += new EventHandler(this.btn_Click);
btnEnviar.Click += new EventHandler(this.btnEnviar_Click);
textbox.Left = 15;
textbox.Top = 20;
textbox.Width = 330;
label.Left = 15;
label.Top = 50;
label.AutoSize = false;
label.Height = 210;
label.Width = 450;
label.BackColor = Color.White;
btnEnviar.Left = 350;
btnEnviar.Top = 17;
btnEnviar.Text = "Enviar";
this.Controls.Add(textbox);
this.Controls.Add(label);
this.Controls.Add(btn);
this.Controls.Add(btnEnviar);
this.SuspendLayout();
this.Name = "MyForm";
this.ResumeLayout(false);
return;
}
}
}
//Cliente.cs
namespace Dia26Prueba {
public class Cliente {
public int creadas;
public int nocreadas;
public List<MyForm> MostrarVentanas(out bool error, ref int creadas, params string[] nombres) {
List<MyForm> list = new List<MyForm>();
int bienCreadas = 0;
foreach (string str in nombres) {
if (str.Length >= 1) {
MyForm mf = new MyForm();
mf.Text = str;
//mf.OwnerChanged += new OwnerChangedEventHandler(mf_OwnerChanged);
list.Add(mf);
mf.Show();
bienCreadas++;
}
}
error = (bienCreadas == creadas);
nocreadas = bienCreadas - creadas;
creadas = bienCreadas;
return list;
}
public void ModificarPosicionYMedidas(MyForm mf, int x = 262, int y = 209, int width = 500, int height = 350) {
mf.Left = x;
mf.Top = y;
mf.Width = width;
mf.Height = height;
}
}
}
// Prueba
namespace Dia29 {
class Prueba {
static void Main(string[] args) {
Cliente cliente = new Cliente();
int n = 4;
Console.WriteLine(cliente.Autor);
if (args.Length != n) {
return;
}
int InstanciasCreadas = n;
bool HayErrores;
List<Dia26.MyForm> list;
list = cliente.MostrarVentanas(
creadas: ref InstanciasCreadas,
error: out HayErrores,
nombres: new string[] { "FirstWindow", "2nd", "3rd", "4th" });
cliente.ModificarPosicionYMedidas(list.ElementAt<MyForm>(0), 0, 0, 512, 384);
cliente.ModificarPosicionYMedidas(list.ElementAt<MyForm>(1), 512, 0, 512, 384);
cliente.ModificarPosicionYMedidas(list.ElementAt<MyForm>(2), 0, 384, 512, 384);
cliente.ModificarPosicionYMedidas(list.ElementAt<MyForm>(3), 512, 384, 512, 384);
for (int i = 0; i < n; i++) {
// .....
Application.Run(list.ElementAt<MyForm>(i));
}
Console.ReadLine();
}
}
}
Here is a small sample. I'm using a interface to remove the coupling between the MainWindow and the ChatWindows.
public class ChatEventArgs : EventArgs
{
public string ChatEventArgs(string message)
{
Message = message;
}
public string Message { get; private set; }
}
public interface IChatMessageProvider
{
event EventHandler<ChatEventArgs> MessageArrived;
void TriggerEvent(object source, ChatEventArgs args);
}
public class MainWindow : IChatMessageProvider
{
public event EventHandler<ChatEventArgs> MessageArrived = delegate{};
public void AddChatWindow()
{
ChatWindow window = new ChatWindow(this);
window.Show();
}
public void TriggerEvent(object source, ChatEventArgs args)
{
MessageArrived(source, args);
}
}
public class ChatWindow :
{
IChatMessageProvider _provider;
public ChatWindow(IChatMessageProvider provider)
{
_provider = provider;
provider.MessageArrived += OnMessage;
}
public void OnMesage(object source, ChatEventArgs args)
{
// since we could have sent the message
if (source == this)
return;
myListBox.Items.Add(args.Message);
}
public void SendButton_Click(object source, EventArgs e)
{
_provider.TriggerEvent(this, new ChatEventArgs(Textbox1.Text));
}
}
There are actualy multiple ways to do it.
Simply make method on Cliente and call it from Prueba. This is simplest and most intuitive solutoin.
Add event to Prueba, pass instance of Prueba to Cliente and let Cliente register to this event.
Use some kind of global static messenger class. Either using events, or simple message passing.