1

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 enter image description here After Resizing, then scrolling down to controls that weren't in visible segment enter image description here And resizing back, then scrolling back up enter image description here

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).

Joe White
  • 94,807
  • 60
  • 220
  • 330
Kraang Prime
  • 9,981
  • 10
  • 58
  • 124
  • What have you try? Unless I understand wrong, because you can create a function to do that handling x,y, width, height. Unless that cause you flickering. Maybe you could include a picture or some code. – Juan Carlos Oropeza Jun 10 '15 at 18:27
  • WinForms?...WebForms?...something else? For WinForms, just add your "bubbles" to a [FlowLayoutPanel](https://msdn.microsoft.com/en-us/library/system.windows.forms.flowlayoutpanel%28v=vs.110%29.aspx) and let it arrange them for you... – Idle_Mind Jun 10 '15 at 20:24
  • Sorry. I did forget to mention, it is WinForms. The FlowLayoutPanel works fairly well, except I lose the left and right indents. (see a typical conversation on Skype or iOS, for what i mean on the indenting) – Kraang Prime Jun 10 '15 at 20:30
  • MessageControl.RedrawControls() has a bug, it changes the Top property but does not pay attention to the scroll position. It must add this.AutoScrollPosition.Y. Same bug in the Add() method. – Hans Passant Jun 10 '15 at 21:21
  • @HansPassant - could you give an example please ? I just added +AutoScrollPosition.Y to both places. Add, and RedrawControls ..... still the exact same problem. – Kraang Prime Jun 10 '15 at 21:23
  • An example??? Just fix the bug. – Hans Passant Jun 10 '15 at 21:26
  • @HansPassant - in case you missed the edit I made, I added it to BOTH `Add` and `RedrawControls`, and the same problem. ZERO change whatsoever (will update code above so you can see) – Kraang Prime Jun 10 '15 at 21:28

3 Answers3

1

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. enter image description here

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++;
}
Juan Carlos Oropeza
  • 47,252
  • 12
  • 78
  • 118
  • Nice job. Im gonna see if I can move the Invalidate and Suspend/Resume to the add method .... – Kraang Prime Jun 10 '15 at 22:11
  • nope, looks like still fail when you shrink to cause multiline text – Juan Carlos Oropeza Jun 10 '15 at 22:12
  • ^ Are you talking about after moving suspend/resume to the Add method, or as it exists in your example above – Kraang Prime Jun 10 '15 at 22:14
  • without my Suspend/Resume the first execute of RedrawControl fail because only execute for 6 element. But the bug when you reduce width to much still fail. But Debug.Writeline will help me to see what is the problem – Juan Carlos Oropeza Jun 10 '15 at 22:17
  • dear god --- ok... i just changed one of the lines to be multiline, and it collapsed in on the others right away ..... until I resized lol --- also... much appreciated :)... control is like 99% completed.... and so useful. – Kraang Prime Jun 10 '15 at 22:18
  • Check again. I just fix it. I dont know if that is a bug but hide element have different height than other element at the moment of redraw. – Juan Carlos Oropeza Jun 10 '15 at 22:37
  • Not sure either, but assigning them all the same height, kinda screws things up as some messages will have more text than others (think of a skype conversation).... there must be a way to do this.... soo close. I wonder if we go old school and say to hell with the built in scrollable control ... and make a control scrollable from scratch – Kraang Prime Jun 10 '15 at 22:39
  • Ups I just realize I assume all the text are same height. That isnt true for all cases – Juan Carlos Oropeza Jun 10 '15 at 22:39
  • Yes, That sound reasonable. But now you know where is the error maybe you can create a new question with the exact behaviour. – Juan Carlos Oropeza Jun 10 '15 at 22:43
  • I found the location of the problem -- not sure what to do about it though. Add a new event override to Message .... OnClick .... and add this ` MessageBox.Show(String.Format("Top: {0}, Height: {0}", this.Location.X, Height));` .... after going through a few, you will notice the Top, and Height are identical on every item. – Kraang Prime Jun 10 '15 at 22:47
  • Include a container on your control to work as fake ScrollBar . Set dock Right. Hide ScrollBar.. Define dummy ScrollBar Width = ScrollBar so this time all element should have same hight. Execture Redraw. Set fake ScrollBar Width to 0. Show ScrollBar again. – Juan Carlos Oropeza Jun 10 '15 at 22:52
  • Already went down the road of adding the messages to a container .... effect was the same. What I mean, is to do the old school method of dropping a scrollbar on the form, and just using that to scroll --- making my own virtual space, as Microsoft clearly shit the bed with their ScrollableContainer, and derivative controls -- unless I am missing something – Kraang Prime Jun 10 '15 at 23:00
  • FOUND IT --- Move the size lines to OnResize, and do suspend/resume layout in there. `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 );` This method, it still buggers up when sized really small quickly, but generally it works perfectly. – Kraang Prime Jun 10 '15 at 23:17
1

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:

Initial Form

The form after being resized smaller:

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:

Scrolled to the bottom

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));
                }
            }
        }

    }

}
Idle_Mind
  • 38,363
  • 3
  • 29
  • 40
  • Looking at it now. Visually, looks pretty sexy, will see what the source yeilds :) -- also, I did get the add and remove working (was a bit tricky as I had to redraw all the controls due to the top area leaving a gap that can't get rid of otherwise. Was actually working on converting the control (message graphic) to richtextbox controls when I saw your response here. – Kraang Prime Jun 11 '15 at 03:24
  • The code works fairly well. Thank you for the shorthand while{} counter, I always forget the shortcut as i'm so used to writing it the other way (older languages). The quirk that I noticed right away, was a drawing error right on launch if the form is smaller than the text (eg, if it needs to wrap text) > http://i.imgur.com/XaPD3tZ.png – Kraang Prime Jun 11 '15 at 03:36
0

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();
    }

enter image description here

Juan Carlos Oropeza
  • 47,252
  • 12
  • 78
  • 118
  • Cool example :) .... this is just adding the controls which I am well past. Give me a few minutes while I wrap up something, then I will post some screenshots. – Kraang Prime Jun 10 '15 at 18:55
  • For the controls that are visible, this sort of works -- like 80% of the time, but in controls that are off the visible area, you can see they get all dorked out. Let me upload my example, I have some sizing stuff done already (thats what I was working on then was gonna give some screenshots) – Kraang Prime Jun 10 '15 at 20:33
  • Looks like I would need more information then in order to help :) – Juan Carlos Oropeza Jun 10 '15 at 21:04
  • I have updated the question with a bunch of details, pictures, and code so with instructions to reproduce and what happens when I size. Do multiple tests when sizing as it is quirky, sometimes it works, sometimes shit just overlaps or buggers up some other way -- in any case, you will definitely be inspired by the randomness of the issue. -- PS .... this is the result of no one releasing or pointing to a functional bubble chat control. The responses thus far have been horrendous in that area, so decided to just fudge around and build it from scratch. – Kraang Prime Jun 10 '15 at 21:08
  • As far as I am aware, this will be the first publicly released WinForms bubble chat control for messaging --- tonnes of bubble controls for a single chat bubble, but none in a form for a conversation. – Kraang Prime Jun 10 '15 at 21:11