I have a set of controls which I am stacking vertically inside a scrollable control.
Each control contains text (like message bubbles on an iPhone), which the bubble resizes based on the height of the text.
The problem I face, is when I resize the parent so it is smaller, the bubbles start to overlap, and when I resize so the bubbles are one-line, there is too much space in between each bubble.
What I would like to do, is to have each bubble snap the top of the bubble to 10pts off the bubble above it, the fastest way possible without any flicker (as there is presently no flicker on resize)
I have thought about embedding each control into another parent (eg, a grid control row), but then each bubble added would be responsible for resizing the parent of itself, and then anchors would no longer work for their top, left, and right positioning.
How can this be done ? (sorry, the details of the question are above as it can't really be worded into a simple one liner question due to the complexity and specifics)
Thanks in advance :)
AS REQUESTED, SCREENSHOTS and CODE
This is the view normally
After Resizing, then scrolling down to controls that weren't in visible segment
And resizing back, then scrolling back up
Now the good stuff..... CODE.....
Here is the code for my custom control:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Runtime.InteropServices;
public class MessageControl : ScrollableControl {
public List<Message> Messages { get; private set; }
private Color _LeftBubbleColor=Color.FromArgb(217,217,217);
private Color _RightBubbleColor=Color.FromArgb(192,206,215);
private Color _LeftBubbleTextColor=Color.FromArgb(52,52,52);
private Color _RightBubbleTextColor=Color.FromArgb( 52, 52, 52 );
private bool _DrawArrow=true;
private int _BubbleIndent=40;
private int _BubbleSpacing=10;
public enum BubblePositionEnum { Left, Right }
public Color LeftBubbleColor { get { return _LeftBubbleColor; } set {_LeftBubbleColor = value; } }
public Color RightBubbleColor { get { return _RightBubbleColor; } set { _RightBubbleColor=value; } }
public Color LeftBubbleTextColor { get { return _LeftBubbleTextColor; } set { _LeftBubbleTextColor=value; } }
public Color RightBubbleTextColor { get { return _RightBubbleTextColor; } set { _RightBubbleTextColor=value; } }
public int BubbleIndent { get { return _BubbleIndent; } set { _BubbleIndent = value; } }
public int BubbleSpacing { get { return _BubbleSpacing; } set { _BubbleSpacing=value; } }
public bool DrawArrow { get { return _DrawArrow; } set { _DrawArrow = value; } }
public MessageControl() {
Messages = new List<Message>();
SetStyle( ControlStyles.AllPaintingInWmPaint|ControlStyles.OptimizedDoubleBuffer|ControlStyles.ResizeRedraw|ControlStyles.SupportsTransparentBackColor|ControlStyles.UserPaint, true );
DoubleBuffered=true;
BackColor=Color.Orange;
Anchor=AnchorStyles.Top|AnchorStyles.Left|AnchorStyles.Right|AnchorStyles.Bottom;
AutoScroll=true;
}
public void Remove( Message message ) {
this.Invalidate();
Messages.Remove( message );
RedrawControls();
}
public void Remove( Message[] messages ) {
this.Invalidate();
foreach ( Message m in messages ) {
Messages.Remove( m );
}
RedrawControls();
}
public void Add( string Message, BubblePositionEnum Position ) {
Message b = new Message(Position);
if ( Messages.Count>0 ) {
b.Top = Messages[Messages.Count-1].Top + Messages[Messages.Count-1].Height + _BubbleSpacing+AutoScrollPosition.Y;
} else {
b.Top = _BubbleSpacing+AutoScrollPosition.Y;
}
b.Text = Message;
b.DrawBubbleArrow=_DrawArrow;
if ( VerticalScroll.Visible ) {
b.Width=Width-( _BubbleIndent+_BubbleSpacing+SystemInformation.VerticalScrollBarWidth );
} else {
b.Width=Width-( _BubbleIndent+_BubbleSpacing );
}
if ( Position==BubblePositionEnum.Right ) {
b.Left = _BubbleIndent;
b.BubbleColor = _RightBubbleColor;
b.ForeColor = _RightBubbleTextColor;
} else {
b.Left = _BubbleSpacing;
b.BubbleColor=_LeftBubbleColor;
b.ForeColor=_LeftBubbleTextColor;
}
Messages.Add(b);
this.Controls.Add(b);
}
protected override void OnResize( System.EventArgs e ) {
RedrawControls();
base.OnResize( e );
}
private void RedrawControls() {
int count=0;
Message last=null;
int new_width=this.Width;
SuspendLayout();
foreach ( Message m in this.Controls ) {
if ( count>0 ) {
m.Top=last.Top+last.Height+_BubbleSpacing+AutoScrollPosition.Y;
if ( VerticalScroll.Visible ) {
m.Width=new_width-( _BubbleIndent+_BubbleSpacing+SystemInformation.VerticalScrollBarWidth );
} else {
m.Width=new_width-( _BubbleIndent+_BubbleSpacing );
}
}
last=m;
count++;
}
ResumeLayout();
Invalidate();
}
public class Message : Control {
private GraphicsPath Shape;
private Color _TextColor=Color.FromArgb( 52, 52, 52 );
private Color _BubbleColor=Color.FromArgb( 217, 217, 217 );
private bool _DrawBubbleArrow=true;
private BubblePositionEnum _BubblePosition = BubblePositionEnum.Left;
public override Color ForeColor { get { return this._TextColor; } set { this._TextColor=value; this.Invalidate(); } }
public BubblePositionEnum BubblePosition { get { return this._BubblePosition; } set { this._BubblePosition=value; this.Invalidate(); } }
public Color BubbleColor { get { return this._BubbleColor; } set { this._BubbleColor=value; this.Invalidate(); } }
public bool DrawBubbleArrow { get { return _DrawBubbleArrow; } set { _DrawBubbleArrow=value; Invalidate(); } }
public Message(BubblePositionEnum Position) {
_BubblePosition=Position;
SetStyle( ControlStyles.AllPaintingInWmPaint|ControlStyles.OptimizedDoubleBuffer|ControlStyles.ResizeRedraw|ControlStyles.SupportsTransparentBackColor|ControlStyles.UserPaint, true );
DoubleBuffered=true;
Size=new Size( 152, 38 );
BackColor=Color.Transparent;
ForeColor=Color.FromArgb( 52, 52, 52 );
Font=new Font( "Segoe UI", 10 );
Anchor=AnchorStyles.Top|AnchorStyles.Left|AnchorStyles.Right;
}
protected override void OnResize( System.EventArgs e ) {
Shape=new GraphicsPath();
var _Shape=Shape;
if ( BubblePosition==BubblePositionEnum.Left ) {
_Shape.AddArc( 9, 0, 10, 10, 180, 90 );
_Shape.AddArc( Width-11, 0, 10, 10, -90, 90 );
_Shape.AddArc( Width-11, Height-11, 10, 10, 0, 90 );
_Shape.AddArc( 9, Height-11, 10, 10, 90, 90 );
} else {
_Shape.AddArc( 0, 0, 10, 10, 180, 90 );
_Shape.AddArc( Width-18, 0, 10, 10, -90, 90 );
_Shape.AddArc( Width-18, Height-11, 10, 10, 0, 90 );
_Shape.AddArc( 0, Height-11, 10, 10, 90, 90 );
}
_Shape.CloseAllFigures();
Invalidate();
base.OnResize( e );
}
protected override void OnPaint( PaintEventArgs e ) {
base.OnPaint( e );
Bitmap B=new Bitmap( this.Width, this.Height );
Graphics G=Graphics.FromImage( B );
SizeF s=G.MeasureString( Text, Font, Width-25 );
this.Height=(int)( Math.Floor( s.Height )+10 );
B=new Bitmap( this.Width, this.Height );
G=Graphics.FromImage( B );
var _G=G;
_G.SmoothingMode=SmoothingMode.HighQuality;
_G.PixelOffsetMode=PixelOffsetMode.HighQuality;
_G.Clear( BackColor );
// Fill the body of the bubble with the specified color
_G.FillPath( new SolidBrush( _BubbleColor ), Shape );
// Draw the string specified in 'Text' property
if ( _BubblePosition==BubblePositionEnum.Left ) {
_G.DrawString( Text, Font, new SolidBrush( ForeColor ), new Rectangle( 13, 4, Width-25, Height-5 ) );
} else {
_G.DrawString( Text, Font, new SolidBrush( ForeColor ), new Rectangle( 5, 4, Width-25, Height-5 ) );
}
// Draw a polygon on the right side of the bubble
if ( _DrawBubbleArrow==true ) {
if(_BubblePosition == BubblePositionEnum.Left) {
Point[] p = {
new Point(9, 9),
new Point(0, 15),
new Point(9, 20)
};
_G.FillPolygon( new SolidBrush( _BubbleColor ), p );
_G.DrawPolygon( new Pen( new SolidBrush( _BubbleColor ) ), p );
} else {
Point[] p = {
new Point(Width - 8, 9),
new Point(Width, 15),
new Point(Width - 8, 20)
};
_G.FillPolygon( new SolidBrush( _BubbleColor ), p );
_G.DrawPolygon( new Pen( new SolidBrush( _BubbleColor ) ), p );
}
}
G.Dispose();
e.Graphics.InterpolationMode=InterpolationMode.HighQualityBicubic;
e.Graphics.DrawImageUnscaled( B, 0, 0 );
B.Dispose();
}
}
}
And for my manifest :
<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel node will disable file and registry virtualization.
If you want to utilize File and Registry Virtualization for backward
compatibility then delete the requestedExecutionLevel node.
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of all Windows versions that this application is designed to work with.
Windows will automatically select the most compatible environment.-->
<!-- If your application is designed to work with Windows Vista, uncomment the following supportedOS node-->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"></supportedOS>
<!-- If your application is designed to work with Windows 7, uncomment the following supportedOS node-->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!-- If your application is designed to work with Windows 8, uncomment the following supportedOS node-->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"></supportedOS>
<!-- If your application is designed to work with Windows 8.1, uncomment the following supportedOS node-->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
</application>
</compatibility>
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<!-- <dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>-->
<asmv1:application>
<asmv1:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true</dpiAware>
</asmv1:windowsSettings>
</asmv1:application>
</asmv1:assembly>
And for the form itself to demo the control on
int x = 0;
while ( x<20 ) {
messageControl1.Add( "Testing", MessageControl.BubblePositionEnum.Right );
messageControl1.Add( "Testing", MessageControl.BubblePositionEnum.Right );
messageControl1.Add( "Testing", MessageControl.BubblePositionEnum.Left );
x++;
}
The form is set to scale to DPI (which is correct, so change that when you edit to test, and use my manifest as that is DPI scaling, not Font scaling).
I think found it.
Just add this line in the Redraw function to realize only 6 object where being updated
Debug.WriteLine(m.Name + "-" + m.Top + "-" + m.Width);
-10-234
-58-217
-106-217
-154-217
-202-217
-250-217
first bug
And this line in the test method fix the creation process
messageControl1.SuspendLayout(); //add
while (x < 20)
{
messageControl1.Add("Testing", MessageControl.BubblePositionEnum.Right);
messageControl1.Add("Testing", MessageControl.BubblePositionEnum.Right);
messageControl1.Add("Testing", MessageControl.BubblePositionEnum.Left);
x++;
}
messageControl1.ResumeLayout(); //add
messageControl1.Invalidate(); //add
As you can see the scroll is at the end.
second bug
Looks like hide elements have diferent size, you can see the debug result.
So I just save firt element Height and assign to everyone.
Debug.WriteLine("------------------------------------------------");
int firstHeight = 0;
foreach (Message m in this.Controls)
{
if (count > 0)
{
Debug.WriteLine(m.Height);
m.Height = firstHeight;
m.Top = last.Top + firstHeight + _BubbleSpacing + AutoScrollPosition.Y;
if (VerticalScroll.Visible)
{
m.Width = new_width - (_BubbleIndent + _BubbleSpacing + SystemInformation.VerticalScrollBarWidth);
}
else
{
m.Width = new_width - (_BubbleIndent + _BubbleSpacing);
}
}
else
{
firstHeight = m.Height;
}
Debug.WriteLine(m.Name + "-" + m.Top + "-" + m.Width);
last = m;
count++;
}
Here's what I'm talking about doing it via the FlowLayoutPanel. Take a close look at all of the code as I've made significant changes throughout. I recommend pasting this over a blank project to play with it.
The form when it initially loads:
The form after being resized smaller:
Here's the form now scrolled to the bottom to show that the FlowLayoutPanel has taken care of re-arranging everything for me:
The re-worked code:
public partial class Form1 : Form
{
public Form1()
{
this.InitializeComponent();
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.SupportsTransparentBackColor | ControlStyles.UserPaint, true);
this.UpdateStyles();
}
private void Form1_Load(object sender, EventArgs e)
{
for (int x = 0; x < 20; x++)
{
messageControl1.Add(x.ToString("00") + ": Testing testing testing ...", MessageControl.BubblePositionEnum.Right);
messageControl1.Add(x.ToString("00") + ": Testing with variable length strings. This one is longer!", MessageControl.BubblePositionEnum.Right);
messageControl1.Add(x.ToString("00") + ": Testing is fun.", MessageControl.BubblePositionEnum.Left);
}
}
}
public class MessageControl : FlowLayoutPanel
{
public List<Message> Messages { get; private set; }
private Color _LeftBubbleColor = Color.FromArgb(217, 217, 217);
private Color _RightBubbleColor = Color.FromArgb(192, 206, 215);
private Color _LeftBubbleTextColor = Color.FromArgb(52, 52, 52);
private Color _RightBubbleTextColor = Color.FromArgb(52, 52, 52);
private bool _DrawArrow = true;
private int _BubbleIndent = 40;
private int _BubbleSpacing = 10;
public enum BubblePositionEnum { Left, Right }
public Color LeftBubbleColor { get { return _LeftBubbleColor; } set { _LeftBubbleColor = value; } }
public Color RightBubbleColor { get { return _RightBubbleColor; } set { _RightBubbleColor = value; } }
public Color LeftBubbleTextColor { get { return _LeftBubbleTextColor; } set { _LeftBubbleTextColor = value; } }
public Color RightBubbleTextColor { get { return _RightBubbleTextColor; } set { _RightBubbleTextColor = value; } }
public int BubbleIndent { get { return _BubbleIndent; } set { _BubbleIndent = value; } }
public int BubbleSpacing { get { return _BubbleSpacing; } set { _BubbleSpacing = value; } }
public bool DrawArrow { get { return _DrawArrow; } set { _DrawArrow = value; } }
public MessageControl()
{
this.Messages = new List<Message>();
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.SupportsTransparentBackColor | ControlStyles.UserPaint, true);
this.UpdateStyles();
this.BackColor = Color.Orange;
this.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom;
this.AutoScroll = true;
}
public void Remove(Message message)
{
this.Messages.Remove(message);
this.Controls.Remove(message);
this.Invalidate();
this.Refresh();
}
public void Remove(Message[] messages)
{
this.SuspendLayout();
foreach (Message m in messages)
{
Messages.Remove(m);
this.Controls.Remove(m);
}
this.ResumeLayout();
this.Invalidate();
this.Refresh();
}
public void Add(string Message, BubblePositionEnum Position)
{
Message b = new Message(this, Message, Position);
b.DrawBubbleArrow = _DrawArrow;
b.Width = this.ClientSize.Width;
Messages.Add(b);
this.Controls.Add(b);
}
protected override void OnLayout(LayoutEventArgs levent)
{
this.ResizeMessages();
base.OnLayout(levent);
}
protected override void OnResize(System.EventArgs e)
{
this.ResizeMessages();
base.OnResize(e);
}
private void ResizeMessages()
{
this.SuspendLayout();
foreach (Message m in this.Messages)
{
m.Width = this.ClientSize.Width - 9;
}
this.ResumeLayout();
}
public class Message : Control
{
private MessageControl _mc;
private GraphicsPath Shape;
private Color _TextColor = Color.FromArgb(52, 52, 52);
private Color _BubbleColor = Color.FromArgb(217, 217, 217);
private bool _DrawBubbleArrow = true;
private BubblePositionEnum _BubblePosition = BubblePositionEnum.Left;
public override Color ForeColor { get { return this._TextColor; } set { this._TextColor = value; this.Invalidate(); } }
public BubblePositionEnum BubblePosition { get { return this._BubblePosition; } set { this._BubblePosition = value; this.Invalidate(); } }
public Color BubbleColor { get { return this._BubbleColor; } set { this._BubbleColor = value; this.Invalidate(); } }
public bool DrawBubbleArrow { get { return _DrawBubbleArrow; } set { _DrawBubbleArrow = value; Invalidate(); } }
private Message() { }
public Message(MessageControl mc, string Message, BubblePositionEnum Position)
{
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.SupportsTransparentBackColor | ControlStyles.UserPaint, true);
this.UpdateStyles();
this._mc = mc;
this._BubblePosition = Position;
this.Text = Message;
this.BubbleColor = (Position == BubblePositionEnum.Right ? mc.RightBubbleColor : mc.LeftBubbleColor);
this.BackColor = this.BubbleColor;
this.ForeColor = (Position == BubblePositionEnum.Right ? mc.RightBubbleTextColor : mc.LeftBubbleTextColor);
this.Font = new Font("Segoe UI", 10);
this.Size = new Size(152, 38);
this.Anchor = AnchorStyles.Left | AnchorStyles.Right;
}
protected override void OnResize(System.EventArgs e)
{
base.OnResize(e);
Shape = new GraphicsPath();
if (BubblePosition == BubblePositionEnum.Left)
{
Shape.AddArc(9, 0, 10, 10, 180, 90);
Shape.AddArc(Width - 10 - this._mc.BubbleIndent, 0, 10, 10, -90, 90);
Shape.AddArc(Width - 10 - this._mc.BubbleIndent, Height - 11, 10, 10, 0, 90);
Shape.AddArc(9, Height - 11, 10, 10, 90, 90);
}
else
{
Shape.AddArc(this._mc._BubbleIndent, 0, 10, 10, 180, 90);
Shape.AddArc(Width - 18, 0, 10, 10, -90, 90);
Shape.AddArc(Width - 18, Height - 11, 10, 10, 0, 90);
Shape.AddArc(this._mc._BubbleIndent, Height - 11, 10, 10, 90, 90);
}
if (_DrawBubbleArrow == true)
{
Point[] p;
if (_BubblePosition == BubblePositionEnum.Left)
{
p = new Point[] {
new Point(9, 9),
new Point(0, 15),
new Point(9, 20)
};
}
else
{
p = new Point[] {
new Point(Width - 8, 9),
new Point(Width, 15),
new Point(Width - 8, 20)
};
}
Shape.AddPolygon(p);
}
Shape.CloseAllFigures();
this.Region = new Region(Shape);
this.Invalidate();
this.Refresh();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var G = e.Graphics;
int RenderWidth = this.Width - 10 - this._mc.BubbleIndent;
SizeF s = G.MeasureString(Text, Font, RenderWidth);
this.Height = (int)(Math.Floor(s.Height) + 10);
G.SmoothingMode = SmoothingMode.HighQuality;
G.PixelOffsetMode = PixelOffsetMode.HighQuality;
G.InterpolationMode = InterpolationMode.HighQualityBicubic;
// Draw the string specified in 'Text' property
using (SolidBrush brush = new SolidBrush(this.ForeColor))
{
if (_BubblePosition == BubblePositionEnum.Left)
{
G.DrawString(Text, Font, brush, new Rectangle(13, 4, RenderWidth, Height - 5));
}
else
{
G.DrawString(Text, Font, brush, new Rectangle(this._mc.BubbleIndent + 5, 4, RenderWidth, Height - 5));
}
}
}
}
}
In this case I'm creating a grid to represent a labyrinth.
My form have a Panel control, then I create buttons inside calculating Top, Left values. For me button size is fixed, you could use your parent value.
I name the buttons grid0102 for row = "01"and col="02"
Then select Location, Size and Text properties.
And finally BackColor for walls
int buttonSize = 20;
Panel myPanel = (Panel)this.Controls["panelArea"];
string[] myGrid = getGrid(0);
for (int row = 0; row < r; row++)
{
char[] rowChar = myGrid[row].ToCharArray();
for (int col = 0; col < c; col++)
{
Button newButton = new Button();
newButton.Name = "grid" + row.ToString("D3") + col.ToString("D3");
newButton.Location = new Point { X = buttonSize * col, Y = buttonSize * row };
newButton.Size = new Size { Width = buttonSize, Height = buttonSize };
newButton.Text = rowChar[col].ToString();
if (rowChar[col] == '%') newButton.BackColor = Color.Green;
myPanel.Controls.Add(newButton);
Debug.WriteLine(newButton.Location);
}
}
NOTE ADDED
But if the problem is handling resize, just encampsule that code in a function and call it when Resize event occurs.
private void panelArea_Resize(object sender, EventArgs e)
{
UI_Resize();
}
Related
I am working on a project wherein I need to add a Control with the shape of a Circle with some text in the middle.
My problem is the circle is too small, when I resize it, it overlaps other controls. I want to draw the circle same width as the square.
Otherwise. how can I make the Control's background transparent?
I am using the code below:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (Bitmap bitmap = new Bitmap(this.Width, this.Height))
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.Clear(this.BackColor);
using (SolidBrush brush = new SolidBrush(this._FillColor))
{
graphics.FillEllipse(brush, 0x18 - 6, 0x18 - 6, (this.Width - 0x30) + 12, (this.Height - 0x30) + 12);
}
Brush FontColor = new SolidBrush(this.ForeColor);
SizeF MS = graphics.MeasureString(Convert.ToString(Convert.ToInt32((100 / _Maximum) * _Value)), Font);
graphics.DrawString(Convert.ToString(Convert.ToInt32((100 / _Maximum) * _Value)), Font, FontColor, Convert.ToInt32((Width / 2 - MS.Width / 2) + 2), Convert.ToInt32((Height / 2 - MS.Height / 2) + 3));
bitmap.MakeTransparent(this.BackColor);
e.Graphics.DrawImage(bitmap, 0, 0);
graphics.Dispose();
bitmap.Dispose();
}
}
}
This is a Custom Control derived from Control, which can be made translucent.
The interface is a colored circle which can contain a couple of numbers.
The Control exposes these custom properties:
Opacity: The level of opacity of the control BackGround [0, 255]
InnerPadding: The distance between the inner rectangle, which defines the circle bounds and the control bounds.
FontPadding: The distance between the Text and the Inner rectangle.
Transparency is obtained overriding CreateParams, then setting ExStyle |= WS_EX_TRANSPARENT;
The Control.SetStyle() method is used to modify the control behavior, adding these ControlStyles:
▶ ControlStyles.Opaque: prevents the painting of a Control's BackGround, so it's not managed by the System. Combined with CreateParams to set the Control's Extended Style to WS_EX_TRANSPARENT, the Control becomes completely transparent.
▶ ControlStyles.SupportsTransparentBackColor the control accepts Alpha values for it's BackGround color. Without also setting ControlStyles.UserPaint it won't be used to simulate transparency. We're doing that ourselves with other means.
To see it at work, create a new Class file, substitute all the code inside with this code preserving the NameSpace and build the Project/Solution.
The new Custom Control will appear in the ToolBox. Drop it on a Form. Modify its custom properties as needed.
A visual representation of the control:
Note and disclaimer:
This is a prototype Control, the custom Designer is missing (cannot post that here, too much code, also connected to a framework).
As presented here, it can be used to completely overlap other Controls in a Form or other containers. Partial overlapping is not handled in this simplified implementation.
The Font is hard-coded to Segoe UI, since this Font has a base-line that simplifies the position of the text in the middle of the circular area.
Other Fonts have a different base-line, which requires more complex handling.
See: TextBox with dotted lines for typing for the base math.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
[DesignerCategory("Code")]
public class RoundCenterLabel : Label, INotifyPropertyChanged, ISupportInitialize
{
private const int WS_EX_TRANSPARENT = 0x00000020;
private bool IsInitializing = false;
private Point MouseDownLocation = Point.Empty;
private readonly int fontPadding = 4;
private Font m_CustomFont = null;
private Color m_BackGroundColor;
private int m_InnerPadding = 0;
private int m_FontPadding = 25;
private int m_Opacity = 128;
public event PropertyChangedEventHandler PropertyChanged;
public RoundCenterLabel() => InitializeComponent();
private void InitializeComponent()
{
SetStyle(ControlStyles.Opaque |
ControlStyles.SupportsTransparentBackColor |
ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
m_CustomFont = new Font("Segoe UI", 50, FontStyle.Regular, GraphicsUnit.Pixel);
BackColor = Color.LimeGreen;
ForeColor = Color.White;
}
protected override CreateParams CreateParams {
get {
var cp = base.CreateParams;
cp.ExStyle |= WS_EX_TRANSPARENT;
return cp;
}
}
public new Font Font
{
get => m_CustomFont;
set {
m_CustomFont = value;
if (IsInitializing) return;
FontAdapter(value, DeviceDpi);
NotifyPropertyChanged();
}
}
public override string Text {
get => base.Text;
set { base.Text = value;
NotifyPropertyChanged();
}
}
public int InnerPadding {
get => m_InnerPadding;
set {
if (IsInitializing) return;
m_InnerPadding = ValidateRange(value, 0, ClientRectangle.Height - 10);
NotifyPropertyChanged(); }
}
public int FontPadding {
get => m_FontPadding;
set {
if (IsInitializing) return;
m_FontPadding = ValidateRange(value, 0, ClientRectangle.Height - 10);
NotifyPropertyChanged();
}
}
public int Opacity {
get => m_Opacity;
set { m_Opacity = ValidateRange(value, 0, 255);
UpdateBackColor(m_BackGroundColor);
NotifyPropertyChanged();
}
}
public override Color BackColor {
get => m_BackGroundColor;
set { UpdateBackColor(value);
NotifyPropertyChanged();
}
}
protected override void OnLayout(LayoutEventArgs e)
{
base.OnLayout(e);
base.AutoSize = false;
}
private void NotifyPropertyChanged([CallerMemberName] string PropertyName = null)
{
InvalidateParent();
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
MouseDownLocation = e.Location;
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.Button == MouseButtons.Left) {
var loc = new Point(Left + (e.X - MouseDownLocation.X), Top + (e.Y - MouseDownLocation.Y));
InvalidateParent();
BeginInvoke(new Action(() => Location = loc));
}
}
private void InvalidateParent()
{
Parent?.Invalidate(Bounds, true);
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
using (var format = new StringFormat(StringFormatFlags.LineLimit | StringFormatFlags.NoWrap, CultureInfo.CurrentUICulture.LCID))
{
format.LineAlignment = StringAlignment.Center;
format.Alignment = StringAlignment.Center;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
using (var circleBrush = new SolidBrush(m_BackGroundColor))
using (var foreBrush = new SolidBrush(ForeColor))
{
FontAdapter(m_CustomFont, e.Graphics.DpiY);
RectangleF rect = InnerRectangle();
e.Graphics.FillEllipse(circleBrush, rect);
e.Graphics.DrawString(Text, m_CustomFont, foreBrush, rect, format);
};
};
}
public void BeginInit() => IsInitializing = true;
public void EndInit()
{
IsInitializing = false;
Font = new Font("Segoe UI", 50, FontStyle.Regular, GraphicsUnit.Pixel);
FontPadding = m_FontPadding;
InnerPadding = m_InnerPadding;
}
private RectangleF InnerRectangle()
{
(float Min, _) = GetMinMax(ClientRectangle.Height, ClientRectangle.Width);
var size = new SizeF(Min - (m_InnerPadding / 2), Min - (m_InnerPadding / 2));
var position = new PointF((ClientRectangle.Width - size.Width) / 2,
(ClientRectangle.Height - size.Height) / 2);
return new RectangleF(position, size);
}
private void FontAdapter(Font font, float dpi)
{
RectangleF rect = InnerRectangle();
float fontSize = ValidateRange(
(int)(rect.Height - m_FontPadding), 6,
(int)(rect.Height - m_FontPadding)) / (dpi / 72.0F) - fontPadding;
m_CustomFont.Dispose();
m_CustomFont = new Font(font.FontFamily, fontSize, font.Style, GraphicsUnit.Pixel);
}
private void UpdateBackColor(Color color)
{
m_BackGroundColor = Color.FromArgb(m_Opacity, Color.FromArgb(color.R, color.G, color.B));
base.BackColor = m_BackGroundColor;
}
private int ValidateRange(int Value, int Min, int Max)
=> Math.Max(Math.Min(Value, Max), Min); // (Value < Min) ? Min : ((Value > Max) ? Max : Value);
private (float, float) GetMinMax(float Value1, float Value2)
=> (Math.Min(Value1, Value2), Math.Max(Value1, Value2));
}
I have a panel that has a collection of custom drawn controls. I know how to programmically scroll the panel but the problem is how the controls draw when scrolling. when scrolling right the control shows as normal but when scrolling left it is not looking correctly. This just the start of the complete application so I just have some basic testing in place right now. Need to find a way for the control to draw correctly when scrolling.
Form code:
public partial class Form1 : Form
{
Panel pn;
private int location = 0;
public Form1()
{
InitializeComponent();
pn = new Panel()
{
Width = this.ClientRectangle.Width - 20,
Height = 120,
BackColor = Color.Black,
Left = 5,
Top = 20
};
pn.AutoScroll = false;
pn.VerticalScroll.Maximum = 100;
pn.HorizontalScroll.Maximum = this.ClientRectangle.Width - 100;
pn.VerticalScroll.Visible = false;
pn.HorizontalScroll.Visible = false;
pn.AutoScrollPosition = new Point(0, 0);
pn.AutoScroll = true;
this.KeyPreview = true;
this.KeyDown += new KeyEventHandler(keyPress);
for(int i = 0; i<10;i++)
{
CustomControl1 cc = null;
if (i % 2 != 0)
cc = new CustomControl1()
{
isOdd = true,
Width = 100,
Height = 100,
Left = (100*i)+5,
Top = 0
};
else
cc = new CustomControl1()
{
isOdd = false,
Width = 100,
Height = 100,
Left = (100 * i) + 5,
Top = 0
};
pn.Controls.Add(cc);
}
this.Controls.Add(pn);
}
private void keyPress(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.A:
if(location - 20 >0)
{
location -= 20;
pn.HorizontalScroll.Value = location;
}
else
{
location = 0;
pn.AutoScrollPosition = new Point(location, 0);
}
break;
case Keys.D:
if(location +20 < pn.HorizontalScroll.Maximum)
{
location += 20;
pn.HorizontalScroll.Value = location;
}
else
{
location = pn.VerticalScroll.Maximum;
pn.AutoScrollPosition = new Point(location, 0);
}
break;
}
foreach(Control c in pn.Controls)
{
c.Invalidate();
}
}
}
Control Code:
public partial class CustomControl1 : Control
{
public bool isOdd { get; set; }
public CustomControl1()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs pe)
{
Graphics gr = pe.Graphics;
Rectangle rc = new Rectangle(pe.ClipRectangle.X,pe.ClipRectangle.Y,100,100);
Brush br = new SolidBrush(Color.Red);
if (isOdd)
br = new SolidBrush(Color.Yellow);
gr.FillEllipse(br, rc);
base.OnPaint(pe);
}
protected override void OnPaintBackground(PaintEventArgs pe)
{
base.OnPaintBackground(pe);
}
}
Don't clip your drawing:
//Rectangle rc = new Rectangle(pe.ClipRectangle.X, pe.ClipRectangle.Y, 100, 100);
Rectangle rc = new Rectangle(0, 0, 100, 100);
I'm using a theme with a custom TabControl control.
The control draws text for each tab using the DrawString method like so:
using (SolidBrush B = new SolidBrush(FC))
{
G.DrawString(TabPages[i].Text, Theme.GlobalFont(FontStyle.Regular, 10), B, new Point(TabRect.X + 50, TabRect.Y + 12));
}
After changing the text to Hebrew, and changing the control's RightToLeftLayout to true, the text is flipped like so:
I tried to search online and found out that you can use Transform to modify the text, however I believe this is not the case since it should be a simple task. Here's the full class for the control:
class FirefoxMainTabControl : TabControl
{
#region " Private "
private Graphics G;
private Rectangle TabRect;
#endregion
private Color FC = Color.Blue;
#region " Control "
public FirefoxMainTabControl()
{
DoubleBuffered = true;
ItemSize = new Size(43, 152);
Alignment = TabAlignment.Left;
SizeMode = TabSizeMode.Fixed;
}
protected override void OnCreateControl()
{
base.OnCreateControl();
SetStyle(ControlStyles.UserPaint, true);
}
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
try
{
for (int i = 0; i <= TabPages.Count - 1; i++)
{
TabPages[i].BackColor = Color.White;
TabPages[i].ForeColor = Color.FromArgb(66, 79, 90);
TabPages[i].Font = Theme.GlobalFont(FontStyle.Regular, 10);
}
}
catch
{
}
}
protected override void OnPaint(PaintEventArgs e)
{
G = e.Graphics;
G.SmoothingMode = SmoothingMode.HighQuality;
G.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
base.OnPaint(e);
G.Clear(Color.FromArgb(66, 79, 90));
for (int i = 0; i <= TabPages.Count - 1; i++)
{
TabRect = GetTabRect(i);
if (SelectedIndex == i)
{
using (SolidBrush B = new SolidBrush(Color.FromArgb(52, 63, 72)))
{
G.FillRectangle(B, TabRect);
}
FC = Helpers.GreyColor(245);
using (SolidBrush B = new SolidBrush(Color.FromArgb(255, 175, 54)))
{
G.FillRectangle(B, new Rectangle(TabRect.Location.X - 3, TabRect.Location.Y + 1, 5, TabRect.Height - 2));
}
}
else
{
FC = Helpers.GreyColor(192);
using (SolidBrush B = new SolidBrush(Color.FromArgb(66, 79, 90)))
{
G.FillRectangle(B, TabRect);
}
}
using (SolidBrush B = new SolidBrush(FC))
{
G.DrawString(TabPages[i].Text, Theme.GlobalFont(FontStyle.Regular, 10), B, new Point(TabRect.X + 50, TabRect.Y + 12));
}
if ((ImageList != null))
{
if (!(TabPages[i].ImageIndex < 0))
{
G.DrawImage(ImageList.Images[TabPages[i].ImageIndex], new Rectangle(TabRect.X + 19, TabRect.Y + ((TabRect.Height / 2) - 10), 18, 18));
}
}
}
}
#endregion
}
I would like to create a control the floats (potentially) outside the bounds of it's containing form. Is this possible? How may I do it?
This would function much like Context Menu's only I need to be able to add other controls to it such as buttons and images.
You want a Form with it's FormBorderStyle set to None, if you want it to behave like a context menu then you'll need to tie showing it to the appropriate event handler in your main form. Simple example below of setting the location and calling show from a mouse click event handler.
MyForm form = new MyForm();
form.Location = PointToScreen(new Point(e.X, e.Y));
form.Show();
It is possible, the TopLevel property controls this. However, the designer doesn't support them well, hard to keep control over controls that are also top-level windows at design time.
Beyond components like ToolTip and ContextMenuStrip, there is exactly one class that is top-level by design, the Form class. Set its FormBorderStyle to None and ControlBox to False to create a basic top-level window that you can use and populate with other controls.
Take a look at the DockPanel Suite source and adopt the technique.
here is u can made for all Control floating Style
private void Panel_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
Panel.Left += e.X - PanelMouseDownLocation.X;
Panel.Top += e.Y - PanelMouseDownLocation.Y;
}
}
private void Panel_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left) PanelMouseDownLocation = e.Location;
}
public Point PanelMouseDownLocation { get; set; }
It would need to be a separate window (much like a context menu actually is) -- you could wrap it as a control, that displays a modeless form (which would even give you the option for non rectangular windows if you really wanted to). As you could create the window from a non-visible control from the parent form, you can maintain a reference to the child for handling inter-form communication.
Have your UserControl override CreateParams. For example:
[DllImport("user32.dll", EntryPoint = "GetDesktopWindow")]
public static extern IntPtr GetDesktopWindow();
protected override CreateParams CreateParams
{
get
{
var cp = base.CreateParams;
cp.ExStyle &= 0x00080000; // WS_EX_LAYERED
cp.Style = 0x40000000 | 0x4000000; // WS_CHILD | WS_CLIPSIBLINGS
cp.Parent = GetDesktopWindow();
return cp;
}
}
This may have unintended effects (including not working well with Designer). I am choosing to follow one of the above patterns, but I thought it was worth mentioning here. Lookup CreateParams to see its purpose. (This option was gleaned from this page.)
This worked for me
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Windows.Forms;
using LollipopUIControls.UIManagers;
namespace Gamasis.Apps.Controls
{
public class FloatingButton : Button
{
public FloatingButton()
{
SetStyle((ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.SupportsTransparentBackColor | ControlStyles.UserPaint), true);
DoubleBuffered = true;
Size = new Size(50, 50);
BackColor = Color.Transparent;
SF.Alignment = StringAlignment.Center;
SF.LineAlignment = StringAlignment.Center;
AnimationTimer.Tick += new EventHandler(AnimationTick);
}
#region Variables
Timer AnimationTimer = new Timer { Interval = 1 };
FontManager font = new FontManager();
StringFormat SF = new StringFormat();
Rectangle StringRectangle;
bool Focus = false;
int margintop = 0, marginleft = 0, marginright = 0, marginBottom = 0;
int xx;
int yy;
float SizeAnimation = 0;
float SizeIncNum;
string fontcolor = "#FAFAFA";
string Backcolor = "#039BE5";
Color EnabledBGColor;
Color EnabledBorderColor;
Color StringColor;
Color DisabledBGColor = ColorTranslator.FromHtml("#B0BEC5");
Color DisabledStringColor = ColorTranslator.FromHtml("#FAFAFA");
Color NonColor = ColorTranslator.FromHtml("#e3e5e7");
Image bGImage = null;
#endregion
#region Properties
[Category("Custom")]
public string BGColor
{
get { return Backcolor; }
set
{
Backcolor = value;
Invalidate();
}
}
[Category("Custom")]
public string FontColor
{
get { return fontcolor; }
set
{
fontcolor = value;
Invalidate();
}
}
[Browsable(false)]
public Font Font
{
get { return base.Font; }
set { base.Font = value; }
}
[Browsable(false)]
public Color ForeColor
{
get { return base.ForeColor; }
set { base.ForeColor = value; }
}
[Category("Custom")]
public Image BGImage
{
get { return bGImage; }
set { bGImage = value; }
}
ImageSizeLevel bGimgSize = ImageSizeLevel.peque2;
public ImageSizeLevel BGimgSize
{
get { return bGimgSize; }
set { bGimgSize = value; }
}
#endregion
#region Events
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
EnabledBGColor = Color.FromArgb(30, ColorTranslator.FromHtml(BGColor));//StringColor);
EnabledBorderColor = Color.FromArgb(20, ColorTranslator.FromHtml(BGColor));//StringColor);
Refresh();
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
EnabledBGColor = ColorTranslator.FromHtml(BGColor);
EnabledBorderColor = ColorTranslator.FromHtml(BGColor);
Refresh();
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
EnabledBGColor = Color.FromArgb(30, StringColor);
Refresh();
xx = e.X;
yy = e.Y;
Focus = true;
AnimationTimer.Start();
Invalidate();
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
Focus = false;
AnimationTimer.Start();
Invalidate();
}
protected override void OnTextChanged(System.EventArgs e)
{
base.OnTextChanged(e);
Invalidate();
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
//StringRectangle = new Rectangle(3, 0, Width - 6, Height - 6);
}
#endregion
protected override void OnResize(System.EventArgs e)
{
base.OnResize(e);
//SizeIncNum = Width / 34;
SizeIncNum = Width / 10;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var G = e.Graphics;
#region Default rectangle
//G.SmoothingMode = SmoothingMode.HighQuality | SmoothingMode.AntiAlias;
//G.Clear(Parent.BackColor);
//StringColor = ColorTranslator.FromHtml(fontcolor);
//var BG = DrawHelper.CreateRoundRect(1, 1, Width - 3, Height - 3, 1);
//Region region = new Region(BG);
//G.FillPath(new SolidBrush(Enabled ? EnabledBGColor : Color.White), BG);
//G.DrawPath(new Pen(Enabled ? EnabledBorderColor : Color.White), BG);
//G.SetClip(region, CombineMode.Replace);
////The Ripple Effect
//G.FillEllipse(new SolidBrush(Color.FromArgb(30, StringColor)), xx - (SizeAnimation / 2), yy - (SizeAnimation / 2), SizeAnimation, SizeAnimation);
//G.DrawString(Text, font.Roboto_Medium10, new SolidBrush(Enabled ? StringColor : DisabledStringColor), R, SF);
#endregion
#region Circle
//G.SmoothingMode = SmoothingMode.AntiAlias;
//G.Clear(BackColor);
//GraphicsPath bgbtn = new GraphicsPath();
//bgbtn.AddEllipse(0, 0, Width - 5, Height - 5);
//GraphicsPath bgShadow = new GraphicsPath();
//bgShadow.AddEllipse(0, 0, Width - 2, Height - 2);
//G.FillPath(new SolidBrush(NonColor), bgShadow);
//G.DrawPath(new Pen(NonColor), bgShadow);
//G.FillPath(new SolidBrush(Color.DeepSkyBlue), bgbtn);
//G.DrawPath(new Pen(Color.DeepSkyBlue), bgbtn);
#endregion
///----------------------------
G.SmoothingMode = SmoothingMode.AntiAlias;
G.Clear(Parent.BackColor);
StringColor = ColorTranslator.FromHtml(fontcolor);
//var BG = DrawHelper.CreateRoundRect(1, 1, Width - 3, Height - 3, 1);
//Círculo principal
GraphicsPath bgbtn = new GraphicsPath();
bgbtn.AddEllipse(2, 0, Width - 6, Height - 6);
//Círculo para la sombra
GraphicsPath bgShadow = new GraphicsPath();
bgShadow.AddEllipse(2, 4, Width - 6, Height - 6);
// se dibuja la sombra
G.FillPath(new SolidBrush(NonColor), bgShadow);
G.DrawPath(new Pen(NonColor), bgShadow);
//sedibuja el círculo principal sobre la sombra
G.FillPath(new SolidBrush(Enabled ? ColorTranslator.FromHtml(BGColor) : DisabledBGColor), bgbtn);
G.DrawPath(new Pen(Enabled ? ColorTranslator.FromHtml(BGColor) : DisabledBGColor), bgbtn);
// Se da a la región forma de círculo/elipse
Region region = new Region(bgbtn);//BG);
G.SetClip(region, CombineMode.Replace);
//The Ripple Effect
if (Enabled)
G.FillEllipse(new SolidBrush(Color.FromArgb(30, EnabledBGColor)), xx - (SizeAnimation / 2), yy - (SizeAnimation / 2), SizeAnimation, SizeAnimation);
StringRectangle = new Rectangle((int)bgbtn.GetBounds().Location.X, (int)bgbtn.GetBounds().Location.Y,
(int)bgbtn.GetBounds().Size.Width, (int)bgbtn.GetBounds().Size.Height);
G.DrawString(Text, font.Roboto_Medium15, new SolidBrush(Enabled ? StringColor : DisabledStringColor), StringRectangle, SF);
if (bGImage != null)
{
float imgX = 0, imgY = 0;
imgY = (bgbtn.GetBounds().Size.Height - (int)bGimgSize) / 2;
imgX = ((bgbtn.GetBounds().Size.Width - (int)bGimgSize) + 2) / 2;
G.DrawImage(bGImage, imgX, imgY, (float)bGimgSize, (float)bGimgSize);
}
}
protected void AnimationTick(object sender, EventArgs e)
{
if (Focus)
{
if (SizeAnimation < Width + 250)
{
SizeAnimation += SizeIncNum;
this.Invalidate();
}
}
else
{
if (SizeAnimation > 0)
{
SizeAnimation = 0;
this.Invalidate();
}
}
}
public enum ImageSizeLevel
{
peque = 12, peque1 = 24, peque2 = 32,
maso = 48, maso1 = 56, maso2 = 64,
grande = 72, grande1 = 86, grande2 = 96,
monstruo = 128, monstruo1 = 256, monstruo2 = 512
}
}
}
I need to create a dropdown menu, or combobox, for a Windows Forms application which contains a small image and then a string of text next to it. Basically, you can think of each 'row' in the dropdown as needing to have an icon and then the name of the icon to the right of the icon. I am having trouble doing this -- in fact, I've been completely unsuccessful. Does anyone know of a way to accomplish this task? Any help will be greatly appreciated. Thanks!
I was able to come up with some very simple code to perform this (see snippet below). The code creates a control that is a dropdown control which shows a small colored square and that color's name in the same row (see photo). Thanks for the links provided for this back when it was originally posted! Hopefully this control can help someone else out in the future.
Image:
Code:
class ColorSelector : ComboBox
{
public ColorSelector()
{
DrawMode = DrawMode.OwnerDrawFixed;
DropDownStyle = ComboBoxStyle.DropDownList;
}
// Draws the items into the ColorSelector object
protected override void OnDrawItem(DrawItemEventArgs e)
{
e.DrawBackground();
e.DrawFocusRectangle();
DropDownItem item = new DropDownItem(Items[e.Index].ToString());
// Draw the colored 16 x 16 square
e.Graphics.DrawImage(item.Image, e.Bounds.Left, e.Bounds.Top);
// Draw the value (in this case, the color name)
e.Graphics.DrawString(item.Value, e.Font, new
SolidBrush(e.ForeColor), e.Bounds.Left + item.Image.Width, e.Bounds.Top + 2);
base.OnDrawItem(e);
}
}
public class DropDownItem
{
public string Value
{
get { return value; }
set { this.value = value; }
}
private string value;
public Image Image
{
get { return img; }
set { img = value; }
}
private Image img;
public DropDownItem() : this("")
{}
public DropDownItem(string val)
{
value = val;
this.img = new Bitmap(16, 16);
Graphics g = Graphics.FromImage(img);
Brush b = new SolidBrush(Color.FromName(val));
g.DrawRectangle(Pens.White, 0, 0, img.Width, img.Height);
g.FillRectangle(b, 1, 1, img.Width - 1, img.Height - 1);
}
public override string ToString()
{
return value;
}
}
Very helpful..
some optimisations :
public sealed class ColorSelector : ComboBox
{
public ColorSelector()
{
DrawMode = DrawMode.OwnerDrawFixed;
DropDownStyle = ComboBoxStyle.DropDownList;
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
e.DrawBackground();
e.DrawFocusRectangle();
if (e.Index >= 0 && e.Index < Items.Count)
{
DropDownItem item = (DropDownItem)Items[e.Index];
e.Graphics.DrawImage(item.Image, e.Bounds.Left, e.Bounds.Top);
e.Graphics.DrawString(item.Value, e.Font, new SolidBrush(e.ForeColor), e.Bounds.Left + item.Image.Width, e.Bounds.Top + 2);
}
base.OnDrawItem(e);
}
}
and ...
public sealed class DropDownItem
{
public string Value { get; set; }
public Image Image { get; set; }
public DropDownItem()
: this("")
{ }
public DropDownItem(string val)
{
Value = val;
Image = new Bitmap(16, 16);
using (Graphics g = Graphics.FromImage(Image))
{
using (Brush b = new SolidBrush(Color.FromName(val)))
{
g.DrawRectangle(Pens.White, 0, 0, Image.Width, Image.Height);
g.FillRectangle(b, 1, 1, Image.Width - 1, Image.Height - 1);
}
}
}
public override string ToString()
{
return Value;
}
}
NOTE: This code is from user que dal's optimization
If you wish to have a string that isn't just the name of the color, change DropDownItem to have 2 arguments, the string and the color, then just change how the brush sets the color, as such:
public DropDownItem(string val, Color color)
{
Value = val;
Image = new Bitmap(16, 16);
using (Graphics g = Graphics.FromImage(Image))
{
using (Brush b = new SolidBrush(color))
{
g.DrawRectangle(Pens.White, 0, 0, Image.Width, Image.Height);
g.FillRectangle(b, 1, 1, Image.Width - 1, Image.Height - 1);
}
}
}
You must then change the dropdown item as such:
public DropDownItem()
: this("", Color.Empty)
{}
Hope this was helpful :)
I resolved the problem, i did this:
ComboBox MarcadorNS = new ComboBox();
MarcadorNS.Height = 30;
MarcadorNS.Width = 150;
MarcadorNS.SelectedValuePath = "Uid";
foreach (var temporalItem in GetPredefinedKinds())
{
Image ImagenCombo = new Image();
ImagenCombo.Source =
new BitmapImage(new Uri(
"Imagenes/Marcadores/" +
temporalItem.Name.ToLower() + ".png", UriKind.Absolute));
ImagenCombo.Height = 28;
ImagenCombo.Width = 28;
ImagenCombo.VerticalAlignment = VerticalAlignment.Top;
ImagenCombo.HorizontalAlignment = HorizontalAlignment.Left;
Label textoCombo = new Label();
textoCombo.VerticalAlignment = VerticalAlignment.Top;
textoCombo.HorizontalAlignment = HorizontalAlignment.Left;
textoCombo.Content = BaseDatos.NombresDeMarcadores(temporalItem.ToString());
Grid GridCombo = new Grid();
GridCombo.Uid = ObtenerMarcador(temporalItem.ToString());
StackPanel stackCombo = new StackPanel();
stackCombo.Orientation = Orientation.Horizontal;
stackCombo.Children.Add(ImagenCombo);
stackCombo.Children.Add(textoCombo);
GridCombo.Children.Add(stackCombo);
MarcadorNS.Items.Add(GridCombo);
}
Not sure about images but this should work for the strings:
comboBox.Items.Add("String");