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
Related
I created a custom Control which inherits from: Panel Control (using C# Class Library) then I used it inside a Form.
When I select any of the Control Properties (during design time), my custom control won’t update !
However if I close the Form and i reopen it; the changes will take place !
I want to have the Control updated when any of the Properties are changed.
Please find bellow my custom control code:
public class PersoCont : Panel
{
private int borderSize = 2;
private Color borderColor = Color.DarkRed;
private bool isBorder = true;
private int paddingTopTitle = 0;
private int paddingBorder = 0;
public int padding_TopTitle
{
get { return paddingTopTitle; }
set { paddingTopTitle = value; Invalidate(); }
}
public int padding_border
{
get { return paddingBorder; }
set { paddingBorder = value; Invalidate(); }
}
public int border_size
{
get { return borderSize; }
set { borderSize = value; Invalidate(); }
}
public Color border_color
{
get { return borderColor; }
set { borderColor = value; Invalidate(); }
}
public bool is_border
{
get { return isBorder; }
set { isBorder = value; Invalidate(); }
}
public PersoCont()
{
}
protected override void OnHandleCreated(EventArgs e)
{
if (this.Controls.Find("xlblTitle", true).Length == 0)
{
if (isBorder == true)
{
Label lblUp = new Label();
lblUp.Text = "";
lblUp.AutoSize = false;
lblUp.BackColor = borderColor;
int lblUpWidth = this.Width - (2 * paddingBorder) - (2 * borderSize);
lblUp.Size = new Size(lblUpWidth, borderSize);
lblUp.Location = new Point(borderSize + paddingBorder, paddingBorder);
lblUp.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right;
Label lblDown = new Label();
lblDown.Text = "";
lblDown.AutoSize = false;
lblDown.BackColor = borderColor;
lblDown.Size = new Size(lblUpWidth, borderSize);
int lblDownTop = this.Height - paddingBorder - borderSize;
lblDown.Location = new Point(borderSize + paddingBorder, lblDownTop);
lblDown.Anchor = AnchorStyles.Left | AnchorStyles.Bottom | AnchorStyles.Right;
Label lblLeft = new Label();
lblLeft.Text = "";
lblLeft.AutoSize = false;
lblLeft.BackColor = borderColor;
int lblLeftHeight = this.Height - (2 * paddingBorder);
lblLeft.Size =new Size(borderSize,lblLeftHeight);
lblLeft.Location = new Point(paddingBorder, paddingBorder);
lblLeft.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Bottom;
Label lblRight = new Label();
lblRight.Text = "";
lblRight.AutoSize = false;
lblRight.BackColor = borderColor;
lblRight.Size = new Size(borderSize, lblLeftHeight);
int lblRightLeft = this.Width - paddingBorder - borderSize;
lblRight.Location = new Point(lblRightLeft, paddingBorder);
lblRight.Anchor = AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom;
this.Controls.Add(lblUp);
this.Controls.Add(lblDown);
this.Controls.Add(lblLeft);
this.Controls.Add(lblRight);
}
}
base.OnHandleCreated(e);
}
}
Use Refresh instead of Invalidate which only signals to the system that a repaint is needed, but won't actually do it: it will be a call to Refresh which will repaint it.
https://learn.microsoft.com/dotnet/api/system.windows.forms.control.refresh
https://learn.microsoft.com/dotnet/api/system.windows.forms.control.update
You may test if the internal value has changed, before calling Refresh to do it (only if necessary):
public int padding_TopTitle
{
get
{
return paddingTopTitle;
}
set
{
if ( paddingTopTitle != value )
{
paddingTopTitle = value;
Refresh();
}
}
}
You may consider using Framework Design Guidelines and C# Naming Conventions.
For example:
public int PaddingTopTitle
{
get
{
return paddingTopTitle;
}
set
{
if ( paddingTopTitle != value )
{
paddingTopTitle = value;
Refresh();
}
}
}
Or:
public int PaddingTopTitle
{
get
{
return _PaddingTopTitle;
}
set
{
if ( _PaddingTopTitle != value )
{
_PaddingTopTitle = value;
Refresh();
}
}
}
I personally prefer _PaddingTopTitle (or _paddingTopTitle) for private field of a property because paddingTopTitle is used for a method parameter as well as local var.
Therefore you may have for example : BorderSize and _BorderSize (or _borderSize).
Thank you for your help.
Initially I used the OnPaint event, but I really didn't like it because it would fire many times (I don't know why).
My problem was solved as follows:
I created a simple private method that will repaint (refresh the graphics data) according to all my properties. Now everything is working fine.
public class PersoCont2 : Panel
{
Label lblUp = null;
bool isBorder;
Color borderColor;
public bool is_border
{
get { return isBorder; }
set { isBorder = value; Rearrange( ); }
}
public Color border_color
{
get { return borderColor; }
set { borderColor = value; Rearrange( ); }
}
protected override void OnHandleCreated(EventArgs e)
{
Rearrange();
}
private void Rearrange( )
{
if( lblUp != null )
{
Controls.Remove( lblUp );
lblUp = null;
}
if( is_border )
{
lblUp = new Label( );
lblUp.BackColor = borderColor;
// TODO: set properties
// . . .
lblUp.Location = new Point( 10, 10 );
Controls.Add( lblUp );
}
}
}
You can try the following code to rewrite panel control and get what you want.
public class MyPanel : Panel
{
private Color colorBorder = Color.Transparent;
public MyPanel()
: base()
{
this.SetStyle(ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.DrawRectangle(
new Pen(
new SolidBrush(colorBorder), 6),
e.ClipRectangle);
}
public Color BorderColor
{
get
{
return colorBorder;
}
set
{
colorBorder = value;
}
}
}
Result:
This behaviour however is a little bit awkward because it will only repaint after clicking the form designer.
I have found the following two label controls that separately handle marquee scrolling of text in a label, and also for the label to be partly transparent. They work very well separately, but I'm having trouble combining them into one control given my limited C#.
Can anyone give me some clues?
Transparent label:
public class LabelTransparent : Label
{
private int opacity;
public Color clrTransparentColor;
public LabelTransparent()
{
this.clrTransparentColor = Color.Blue;
this.opacity = 50;
this.BackColor = Color.Transparent;
}
protected override void OnPaint(PaintEventArgs e)
{
if (Parent != null)
{
using (var bmp = new Bitmap(Parent.Width, Parent.Height))
{
Parent.Controls.Cast<Control>()
.Where(c => Parent.Controls.GetChildIndex(c) > Parent.Controls.GetChildIndex(this))
.Where(c => c.Bounds.IntersectsWith(this.Bounds))
.OrderByDescending(c => Parent.Controls.GetChildIndex(c))
.ToList()
.ForEach(c => c.DrawToBitmap(bmp, c.Bounds));
e.Graphics.DrawImage(bmp, -Left, -Top);
using (var b = new SolidBrush(Color.FromArgb(this.Opacity, this.TransparentBackColor)))
{
e.Graphics.FillRectangle(b, this.ClientRectangle);
}
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
TextRenderer.DrawText(e.Graphics, this.Text, this.Font, this.ClientRectangle, this.ForeColor, Color.Transparent);
}
}
}
public int Opacity
{
get { return opacity; }
set { if (value >= 0 && value <= 255) opacity = value; this.Invalidate(); }
}
public Color TransparentBackColor
{
get { return clrTransparentColor; }
set { clrTransparentColor = value; this.Invalidate(); }
}
[Browsable(false)]
public override Color BackColor
{
get { return Color.Transparent; }
set { base.BackColor = Color.Transparent; }
}
}
Marquee scrolling:
public class LabelMarquee : Label
{
private int CurrentPosition { get; set; }
private Timer Timer = new Timer();
private Graphics grText;
private float fTextPixels;
public int ScrollSpeed
{
get { return this.Timer.Interval; }
set { try { this.Timer.Interval = value; } catch (Exception) { this.Timer.Interval = 15; } }
}
public LabelMarquee()
{
UseCompatibleTextRendering = true;
grText = this.CreateGraphics();
Timer.Tick += new EventHandler(Timer_Tick);
Timer.Start();
}
void Timer_Tick(object sender, EventArgs e)
{
fTextPixels = grText.MeasureString(this.Text, this.Font).Width;
if (CurrentPosition < -this.fTextPixels) CurrentPosition = Width;
else CurrentPosition -= 1;
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.TranslateTransform((float)CurrentPosition, 0);
base.OnPaint(e);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (Timer != null)
Timer.Dispose();
}
Timer = null;
}
}
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 have customized the GroupBox to make it collapsable/expandable.
Code is working fine. But I want to initialize this GroupBox in collapsed form instead of expendable. I tried to set values (m_collapsed = true/false and m_bResizingFromCollapse = true/false)and override OnPaint, but nothing is working.
namespace ABC
{
/// <summary>
/// GroupBox control that provides functionality to
/// allow it to be collapsed.
/// </summary>
[ToolboxBitmap(typeof(CollapsibleGroupBox))]
public partial class CollapsibleGroupBox : GroupBox
{
#region Fields
private Rectangle m_toggleRect = new Rectangle(8, 2, 11, 11);
private Boolean m_collapsed = false;
private Boolean m_bResizingFromCollapse = false;
private const int m_collapsedHeight = 20;
private Size m_FullSize = Size.Empty;
#endregion
#region Events & Delegates
/// <summary>Fired when the Collapse Toggle button is pressed</summary>
public delegate void CollapseBoxClickedEventHandler(object sender);
public event CollapseBoxClickedEventHandler CollapseBoxClickedEvent;
#endregion
#region Constructor
public CollapsibleGroupBox()
{
InitializeComponent();
}
#endregion
#region Public Properties
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int FullHeight
{
get { return m_FullSize.Height; }
}
[DefaultValue(false), Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool IsCollapsed
{
get { return m_collapsed; }
set
{
if (value != m_collapsed)
{
m_collapsed = value;
if (!value)
// Expand
this.Size = m_FullSize;
else
{
// Collapse
m_bResizingFromCollapse = true;
this.Height = m_collapsedHeight;
m_bResizingFromCollapse = false;
}
foreach (Control c in Controls)
c.Visible = !value;
Invalidate();
}
}
}
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int CollapsedHeight
{
get { return m_collapsedHeight; }
}
#endregion
#region Overrides
protected override void OnMouseUp(MouseEventArgs e)
{
if (m_toggleRect.Contains(e.Location))
ToggleCollapsed();
else
base.OnMouseUp(e);
}
protected override void OnPaint(PaintEventArgs e)
{
HandleResize();
DrawGroupBox(e.Graphics);
DrawToggleButton(e.Graphics);
}
#endregion
#region Implimentation
void DrawGroupBox(Graphics g)
{
// Get windows to draw the GroupBox
Rectangle bounds = new Rectangle(ClientRectangle.X, ClientRectangle.Y + 6, ClientRectangle.Width, ClientRectangle.Height - 6);
GroupBoxRenderer.DrawGroupBox(g, bounds, Enabled ? GroupBoxState.Normal : GroupBoxState.Disabled);
// Text Formating positioning & Size
StringFormat sf = new StringFormat();
int i_textPos = (bounds.X + 8) + m_toggleRect.Width + 2;
int i_textSize = (int)g.MeasureString(Text, this.Font).Width;
i_textSize = i_textSize < 1 ? 1 : i_textSize;
int i_endPos = i_textPos + i_textSize + 1;
// Draw a line to cover the GroupBox border where the text will sit
g.DrawLine(SystemPens.Control, i_textPos, bounds.Y, i_endPos, bounds.Y);
// Draw the GroupBox text
using (SolidBrush drawBrush = new SolidBrush(Color.FromArgb(0, 70, 213)))
g.DrawString(Text, this.Font, drawBrush, i_textPos, 0);
}
void DrawToggleButton(Graphics g)
{
if (IsCollapsed)
{
Image plusImage = Image.FromFile(Path.Combine(GameManager.sAssetsDir, "plus.bmp"));
g.DrawImage(plusImage, m_toggleRect);
}
else
{
Image minusImage = Image.FromFile(Path.Combine(GameManager.sAssetsDir, "minus.bmp"));
g.DrawImage(minusImage, m_toggleRect);
}
}
void ToggleCollapsed()
{
IsCollapsed = !IsCollapsed;
if (CollapseBoxClickedEvent != null)
CollapseBoxClickedEvent(this);
}
void HandleResize()
{
if (!m_bResizingFromCollapse && !m_collapsed)
m_FullSize = this.Size;
}
#endregion
}
}
I am trying to set a tooltip on an arbitrary range of text on a richtextbox. Is this possible? If so how would I do it e.g by passing in parameters "from" and "to" as (int) indexes.
Thanks
You could use the following as a starting point:
Add a reference to System.Windows.Interactivity.
Add the following classes to your project:
public class TextRangeToolTip
{
public int StartPosition { get; set; }
public int Length { get; set; }
public object ToolTip { get; set; }
internal bool IsInRange(int position)
{
return this.StartPosition <= position && position < this.StartPosition + this.Length;
}
}
public class TextRangeToolTipCollection : ObservableCollection<TextRangeToolTip> {}
[ContentProperty("Ranges")]
public class ToolTipBehavior : Behavior<RichTextBox>
{
private const int ToolTipHideMouseDelta = 9;
public static readonly DependencyProperty RangesProperty
= DependencyProperty.Register("Ranges", typeof(TextRangeToolTipCollection),
typeof (ToolTipBehavior),
new PropertyMetadata(OnRangesChanged));
private readonly DispatcherTimer timer;
private readonly ToolTip toolTip;
private Point lastMousePosition;
public TextRangeToolTipCollection Ranges
{
get
{
return (TextRangeToolTipCollection)this.GetValue(RangesProperty)
?? (this.Ranges = new TextRangeToolTipCollection());
}
set { this.SetValue(RangesProperty, value); }
}
public ToolTipBehavior()
{
this.Ranges = new TextRangeToolTipCollection();
this.timer = new DispatcherTimer();
this.timer.Tick += this.TimerOnTick;
this.timer.Interval = TimeSpan.FromSeconds(1);
this.toolTip = new ToolTip {Placement = PlacementMode.Relative};
}
protected override void OnAttached()
{
this.AssociatedObject.ToolTip = this.toolTip;
this.toolTip.PlacementTarget = this.AssociatedObject;
ToolTipService.SetIsEnabled(this.AssociatedObject, false);
this.AssociatedObject.MouseMove += this.AssociatedObjectOnMouseMove;
}
protected override void OnDetaching()
{
this.timer.Stop();
this.toolTip.PlacementTarget = null;
this.AssociatedObject.ToolTip = null;
this.AssociatedObject.ClearValue(ToolTipService.IsEnabledProperty);
this.AssociatedObject.MouseMove -= this.AssociatedObjectOnMouseMove;
}
private void AssociatedObjectOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
{
Point currentMousePosition = mouseEventArgs.GetPosition(this.AssociatedObject);
if (this.AssociatedObject.IsMouseCaptured)
{
Vector delta = currentMousePosition
- this.lastMousePosition;
if (delta.X*delta.X + delta.Y*delta.Y <= ToolTipHideMouseDelta)
{
this.toolTip.HorizontalOffset = currentMousePosition.X + 10;
this.toolTip.VerticalOffset = currentMousePosition.Y + 10;
return;
}
this.AssociatedObject.ReleaseMouseCapture();
this.toolTip.IsOpen = false;
}
if (this.AssociatedObject.IsMouseOver)
{
this.lastMousePosition = currentMousePosition;
this.timer.Stop();
this.timer.Start();
}
}
private static void OnRangesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ToolTipBehavior) d).OnRangesChanged((IEnumerable<TextRangeToolTip>) e.OldValue,
(IEnumerable<TextRangeToolTip>) e.NewValue);
}
private void OnRangesChanged(IEnumerable<TextRangeToolTip> oldRanges, IEnumerable<TextRangeToolTip> newRanges)
{
var oldObservable = oldRanges as INotifyCollectionChanged;
if (oldObservable != null)
{
CollectionChangedEventManager.RemoveHandler(oldObservable, this.OnRangesCollectionChanged);
}
var newObservable = newRanges as INotifyCollectionChanged;
if (newObservable != null)
{
CollectionChangedEventManager.AddHandler(newObservable, this.OnRangesCollectionChanged);
}
this.UpdateToolTip();
}
private void OnRangesCollectionChanged(
object sender,
NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
this.UpdateToolTip();
}
private bool SetToolTipData()
{
if (this.Ranges == null)
{
return false;
}
TextPointer pointer = this.AssociatedObject.GetPositionFromPoint(this.lastMousePosition, false);
if (pointer == null)
{
return false;
}
int position = this.AssociatedObject.Document.ContentStart.GetOffsetToPosition(pointer);
TextRangeToolTip matchingRange = this.Ranges.FirstOrDefault(r => r.IsInRange(position));
if (matchingRange == null)
{
return false;
}
this.toolTip.Content = matchingRange.ToolTip;
return true;
}
private void TimerOnTick(object sender, EventArgs eventArgs)
{
this.timer.Stop();
if (this.AssociatedObject.IsMouseOver && this.SetToolTipData())
{
this.toolTip.IsOpen = true;
this.AssociatedObject.CaptureMouse();
}
}
private void UpdateToolTip()
{
if (this.AssociatedObject != null && this.AssociatedObject.IsMouseCaptured && !this.SetToolTipData())
{
this.toolTip.IsOpen = false;
this.AssociatedObject.ReleaseMouseCapture();
}
}
}
Use it on your RichTextBox like this:
<RichTextBox>
<i:Interaction.Behaviors>
<myapp:ToolTipBehavior>
<myapp:TextRangeToolTip StartPosition="10" Length="4" ToolTip="some" />
<myapp:TextRangeToolTip StartPosition="15" Length="4" ToolTip="text" />
</myapp:ToolTipBehavior>
</i:Interaction.Behaviors>
<FlowDocument>
<Paragraph>This is some text. This is some other text.</Paragraph>
</FlowDocument>
</RichTextBox>
Alternatively, you can bind a TextRangeToolTipCollection to the Ranges property like this:
<RichTextBox Document="{Binding Document}">
<i:Interaction.Behaviors>
<myapp:ToolTipBehavior Ranges="{Binding RangeToolTips}" />
</i:Interaction.Behaviors>
</RichTextBox>
Getting the positions right is a bit tricky, because WPF counts symbols, not characters. You could extend the TextRangeToolTip class to have properties of type TextPointer or TextRange and construct it using your FlowDocument instance.