I use this program: https://www.codeproject.com/Articles/24681/WPF-Diagram-Designer-Part-4 and change it a bit to make a new program to draw graph(mathmatical graph) but not completed.
for each DesignerItem I add a Text as Adorner(TextAdorner class), you can see this at: https://i.stack.imgur.com/VDvGQ.png
but when two or more DesignerItems overlaped all text mixed together, because Adorner is top of all DesignerItems in canvas.
There is a way to make an adorner below of a DesignerItem in canvas?
You can see TextAdorner class below.
class TextAdorner : Adorner
{
VisualCollection _visualChildren;
Path _textGeometry;
TexFormula formula;
TexFormulaParser parser;
public Brush ForeColor
{ get; set; }
public double FontSize
{ get; set; }
public TextAdorner(UIElement adornedElement)
: base(adornedElement)
{
_visualChildren = new VisualCollection(this);
this.Tag = #"v^{b_i}_{c_j}";
parser = new TexFormulaParser();
formula = parser.Parse(this.Tag.ToString());
_textGeometry=new Path();
this.ForeColor = Brushes.Black;
this.FontSize = 32;
_textGeometry.Fill = this.ForeColor;
_textGeometry.Stroke = this.ForeColor;
_textGeometry.StrokeThickness = 1;
_visualChildren.Add(_textGeometry);
this.HorizontalAlignment = System.Windows.HorizontalAlignment.Center;
}
protected override Size ArrangeOverride(Size finalSize)
{
double _left;
double _top;
_textGeometry.Data = formula.GetRenderer(TexStyle.Display, this.FontSize, "Times New Roman").RenderToGeometry(0, 0);
switch (this.HorizontalAlignment)
{
case System.Windows.HorizontalAlignment.Left:
{
_left = 0;
break;
}
case System.Windows.HorizontalAlignment.Right:
{
_left = AdornedElement.RenderSize.Width - _textGeometry.RenderedGeometry.Bounds.Width;
break;
}
default:
{
_left = AdornedElement.RenderSize.Width / 2 - _textGeometry.RenderedGeometry.Bounds.Width / 2;
break;
}
}
switch (this.VerticalAlignment)
{
case System.Windows.VerticalAlignment.Top:
{
_top = 0;
break;
}
case System.Windows.VerticalAlignment.Bottom:
{
_top = AdornedElement.RenderSize.Height- _textGeometry.RenderedGeometry.Bounds.Height;
break;
}
default:
{
_top = AdornedElement.RenderSize.Height / 2 - _textGeometry.RenderedGeometry.Bounds.Height/ 2;
break;
}
}
Rect _leftTopRect = new Rect(_left,_top,AdornedElement.RenderSize.Width,AdornedElement.RenderSize.Height);
_textGeometry.Arrange(_leftTopRect);
return finalSize;
}
protected override int VisualChildrenCount
{
get { return _visualChildren.Count; }
}
protected override Visual GetVisualChild(int index)
{
return _visualChildren[index];
}
}
Related
I have here the circle image, what I want to do is to put some color in specific position.. For example, when I click the button1, the left side of the circle will be filled by red, and when I click the button2, the right side will be filled by as well, and when I click the button1 again, the color will be removed, and so on...
I've done some research about it, and found out 2 ways to do it. First is, to layover the circle with another image. Second is to draw, and use the Graphics class in C#..
Now, my question is, is there another possible way to do it? What is the best way?
P.S: The purpose of this is for the tooth chart. :)
Here's a Resizable, Clickable, UserControl based on qing`s post. You can click on the regions directly to toggle them, or change them via code.
public partial class ToothChart : UserControl
{
public ToothChart()
{
InitializeComponent();
this.DoubleBuffered = true;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (this.ParentForm != null)
{
this.ParentForm.FormClosing += (s, evt) => { OnHandleDestroyed(new EventArgs()); };
}
}
protected override void OnHandleDestroyed(EventArgs e)
{
base.OnHandleDestroyed(e);
if (this._pathTop != null)
{
this._pathTop.Dispose();
this._pathTop = null;
}
if (this._pathRight != null)
{
this._pathRight.Dispose();
this._pathRight = null;
}
if (this._pathBottom != null)
{
this._pathBottom.Dispose();
this._pathBottom = null;
}
if (this._pathLeft != null)
{
this._pathLeft.Dispose();
this._pathLeft = null;
}
if (this._pathCenter != null)
{
this._pathCenter.Dispose();
this._pathCenter = null;
}
}
private GraphicsPath _pathTop = null;
private GraphicsPath _pathLeft = null;
private GraphicsPath _pathBottom = null;
private GraphicsPath _pathRight = null;
private GraphicsPath _pathCenter = null;
private bool _TopRegion = false;
public bool TopRegion
{
get
{
return _TopRegion;
}
set
{
if (_TopRegion != value)
{
_TopRegion = value;
this.Invalidate();
}
}
}
private bool _RightRegion = false;
public bool RightRegion
{
get
{
return _RightRegion;
}
set
{
if (_RightRegion != value)
{
_RightRegion = value;
this.Invalidate();
}
}
}
private bool _BottomRegion = false;
public bool BottomRegion
{
get
{
return _BottomRegion;
}
set
{
if (_BottomRegion != value)
{
_BottomRegion = value;
this.Invalidate();
}
}
}
private bool _LeftRegion = false;
public bool LeftRegion
{
get
{
return _LeftRegion;
}
set
{
if (_LeftRegion != value)
{
_LeftRegion = value;
this.Invalidate();
}
}
}
private bool _CenterRegion = false;
public bool CenterRegion
{
get
{
return _CenterRegion;
}
set
{
if (_CenterRegion != value)
{
_CenterRegion = value;
this.Invalidate();
}
}
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
if (this.IsHandleCreated && this._pathTop != null)
{
this.UpdateRegions();
}
}
private void UpdateRegions()
{
int diameterBig = Math.Min(this.Width, this.Height) - 10;
int diameterSmall = Math.Min(this.Width, this.Height) / 3;
if (diameterBig > 0 && diameterSmall > 0)
{
Point _centerPoint = new Point(this.Width / 2, this.Height / 2);
Rectangle rectangle = new Rectangle(_centerPoint.X - diameterBig / 2, _centerPoint.Y - diameterBig / 2, diameterBig, diameterBig);
Rectangle rectangle2 = new Rectangle(_centerPoint.X - diameterSmall / 2, _centerPoint.Y - diameterSmall / 2, diameterSmall, diameterSmall);
_pathTop.Reset();
_pathTop.AddArc(rectangle, 225, 90);
_pathTop.AddArc(rectangle2, -45, -90);
_pathLeft.Reset();
_pathLeft.AddArc(rectangle, 135, 90);
_pathLeft.AddArc(rectangle2, -135, -90);
_pathBottom.Reset();
_pathBottom.AddArc(rectangle, 45, 90);
_pathBottom.AddArc(rectangle2, -225, -90);
_pathRight.Reset();
_pathRight.AddArc(rectangle, -45, 90);
_pathRight.AddArc(rectangle2, -315, -90);
_pathCenter.Reset();
_pathCenter.AddEllipse(rectangle2);
this.Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e)
{
if (this.IsHandleCreated)
{
if (this._pathTop == null)
{
this._pathTop = new GraphicsPath();
this._pathRight = new GraphicsPath();
this._pathBottom = new GraphicsPath();
this._pathLeft = new GraphicsPath();
this._pathCenter = new GraphicsPath();
this.UpdateRegions();
}
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
if (this.TopRegion)
{
e.Graphics.FillPath(Brushes.Blue, _pathTop);
}
e.Graphics.DrawPath(Pens.Black, _pathTop);
if (this.RightRegion)
{
e.Graphics.FillPath(Brushes.DarkRed, _pathRight);
}
e.Graphics.DrawPath(Pens.Black, _pathRight);
if (this.BottomRegion)
{
e.Graphics.FillPath(Brushes.Teal, _pathBottom);
}
e.Graphics.DrawPath(Pens.Black, _pathBottom);
if (this.LeftRegion)
{
e.Graphics.FillPath(Brushes.Yellow, _pathLeft);
}
e.Graphics.DrawPath(Pens.Black, _pathLeft);
if (this.CenterRegion)
{
e.Graphics.FillPath(Brushes.LightGreen, _pathCenter);
}
e.Graphics.DrawPath(Pens.Black, _pathCenter);
}
}
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
Point p = new Point(e.X, e.Y);
if (this._pathTop.IsVisible(p))
{
this.TopRegion = !this.TopRegion;
}
else if (this._pathRight.IsVisible(p))
{
this.RightRegion = !this.RightRegion;
}
else if (this._pathBottom.IsVisible(p))
{
this.BottomRegion = !this.BottomRegion;
}
else if (this._pathLeft.IsVisible(p))
{
this.LeftRegion = !this.LeftRegion;
}
else if (this._pathCenter.IsVisible(p))
{
this.CenterRegion = !this.CenterRegion;
}
}
}
I am developing a custom combobox control dervied from the standard combobox that will look flat and borderless based on the article here:
http://www.codeproject.com/Articles/6971/Making-Standard-ComboBox-appear-flat
According to various sources I will have to override the windows procedure to do that so here is my WndProc override where I have blocked several messages from reaching the control:
protected override void WndProc(ref Message m)
{
IntPtr hDC = IntPtr.Zero;
Graphics gdc = null;
switch (m.Msg)
{
// Block these messages
case WM_CHANGEUISTATE:
case WM_MOUSEACTIVATE:
case WM_MOUSEFIRST:
case WM_MOUSELAST:
case WM_MOUSEHOVER:
case WM_MOUSELEAVE:
m.Result = (IntPtr)1;
break;
// Here we paint the border when non-client paint is received
case WM_NC_PAINT:
hDC = GetWindowDC(this.Handle);
gdc = Graphics.FromHdc(hDC);
SendMessage(this.Handle, WM_ERASEBKGND, hDC, 0);
SendPrintClientMsg(); // Send to draw client area
PaintFlatControlBorder(this, gdc);
m.Result = (IntPtr) 1; // Indicate message has been processed
ReleaseDC(m.HWnd, hDC);
gdc.Dispose();
break;
case WM_PAINT:
base.WndProc(ref m);
// Flatten the border area again
hDC = GetWindowDC(this.Handle);
gdc = Graphics.FromHdc(hDC);
Pen p = new Pen((this.Enabled? BackColor:SystemColors.Control), 2);
gdc.DrawRectangle(p, new Rectangle(2, 2, this.Width-3, this.Height-3));
PaintFlatDropDown(this, gdc);
PaintFlatControlBorder(this, gdc);
ReleaseDC(m.HWnd, hDC);
gdc.Dispose();
break;
default:
base.WndProc(ref m);
break;
}
}
Here is my constructor:
public FlatComboBox() : base()
{
this.FlatStyle = FlatStyle.Flat;
this.SetStyle(ControlStyles.DoubleBuffer | ControlStyles.ResizeRedraw, true);
}
What other wndproc messages do I have to block or is there another way around this?
Here is a good reference of all wndproc messages:
https://www.autoitscript.com/autoit3/docs/appendix/WinMsgCodes.htm
I am using .NET v4.5.
Thanks in advance.
I have done this before with a combo box to give it a customisable flat appearance, I am showing the full code so that is easier to get the idea of how it works, however it uses a custom drop down menu and menu items to complete the appearance.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace Custom.Controls
{
public class CustomComboBox : UserControl
{
private const int DEFAULT_WIDTH = 121;
private const int DEFAULT_HEIGHT = 20;
private CustomContextMenuStrip _popupControl;
private CustomToolStripMenuItem _selectedItem;
private bool _bDroppedDown;
private Color _borderColour;
private Color _dropDownButtonColour;
private Color _droppedDownArrowColour;
private Color _closedArrowColour;
private Color _dropDownBackColour;
private Color _dropDownForeColour;
private Color _dropDownBorderColour;
public Color BorderColour
{
get
{
return _borderColour;
}
set
{
_borderColour = value;
this.Invalidate();
}
}
public Color ClosedArrowColour
{
get
{
return _closedArrowColour;
}
set
{
_closedArrowColour = value;
this.Invalidate();
}
}
public Color DroppedDownArrowColour
{
get
{
return _droppedDownArrowColour;
}
set
{
_droppedDownArrowColour = value;
this.Invalidate();
}
}
public Color DropDownButtonColour
{
get
{
return _dropDownButtonColour;
}
set
{
_dropDownButtonColour = value;
this.Invalidate();
}
}
public Color DropDownBorderColour
{
get
{
return _dropDownBorderColour;
}
set
{
_dropDownBorderColour = value;
_popupControl.BorderColour = value;
this.Invalidate();
}
}
public Color DropDownBackColour
{
get
{
return _dropDownBackColour;
}
set
{
_dropDownBackColour = value;
_popupControl.BackColor = value;
this.Invalidate();
}
}
public Color DropDownForeColour
{
get
{
return _dropDownForeColour;
}
set
{
_dropDownForeColour = value;
_popupControl.ForeColor = value;
this.Invalidate();
}
}
public bool DropShadowEnabled
{
get
{
return _popupControl.DropShadowEnabled;
}
set
{
_popupControl.DropShadowEnabled = value;
this.Invalidate();
}
}
public bool DroppedDown
{
get
{
return _bDroppedDown;
}
set
{
_bDroppedDown = value;
if (value)
{
_popupControl.Show(this, new Point(0, this.Height), ToolStripDropDownDirection.BelowRight);
}
else
{
_popupControl.Hide();
}
this.Invalidate();
}
}
public ToolStripItemCollection Items
{
get
{
return _popupControl.Items;
}
}
public CustomToolStripMenuItem SelectedItem
{
get
{
return _selectedItem;
}
}
public CustomComboBox()
{
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.Selectable, true);
this.SetStyle(ControlStyles.UserMouse, true);
this.SetStyle(ControlStyles.UserPaint, true);
this.SuspendLayout();
this.ResumeLayout(false);
_popupControl = new CustomContextMenuStrip();
_popupControl.BackColor = this.BackColor;
_popupControl.Closed += new ToolStripDropDownClosedEventHandler(PopupControl_Closed);
_popupControl.ItemClicked += new ToolStripItemClickedEventHandler(PopupControl_ItemClicked);
this.Width = DEFAULT_WIDTH;
this.Height = DEFAULT_HEIGHT;
_selectedItem = null;
_bDroppedDown = false;
this.BackColor = SystemColors.Control;
this.ForeColor = SystemColors.ControlText;
_borderColour = SystemColors.ActiveBorder;
_dropDownButtonColour = SystemColors.ButtonFace;
_droppedDownArrowColour = SystemColors.ControlLight;
_closedArrowColour = SystemColors.ControlDark;
//Set these via the properties so they take effect on _popupControl
this.DropDownBackColour = SystemColors.Control;
this.DropDownForeColour = SystemColors.ControlText;
this.DropDownBorderColour = SystemColors.ActiveBorder;
}
protected override void OnFontChanged(EventArgs e)
{
_popupControl.Font = this.Font;
base.OnFontChanged(e);
}
protected override void OnMouseDown(MouseEventArgs e)
{
this.Invalidate();
this.DroppedDown = !this.DroppedDown;
base.OnMouseDown(e);
}
protected override void OnMouseWheel(MouseEventArgs e)
{
int nIndex = this.Items.IndexOf(_selectedItem);
if (e.Delta < 0)
{
if (nIndex < (this.Items.Count - 1))
{
_selectedItem = (CustomToolStripMenuItem)this.Items[nIndex + 1];
OnSelectedItemChanged(EventArgs.Empty);
this.Invalidate();
}
}
else if (e.Delta > 0)
{
if (nIndex > 0)
{
_selectedItem = (CustomToolStripMenuItem)this.Items[nIndex - 1];
OnSelectedItemChanged(EventArgs.Empty);
this.Invalidate();
}
}
base.OnMouseWheel(e);
}
protected override void OnPaint(PaintEventArgs e)
{
Rectangle boundingRect = new Rectangle(0, 0, this.Width, this.Height);
using (SolidBrush brush = new SolidBrush(this.BackColor))
{
e.Graphics.FillRectangle(brush, boundingRect);
}
ControlPaint.DrawBorder(e.Graphics, boundingRect, _borderColour, ButtonBorderStyle.Solid);
//Draw the dropdown button background
int nButtonWidth = SystemInformation.VerticalScrollBarWidth + SystemInformation.VerticalFocusThickness;
Rectangle dropDownButtonRect = new Rectangle(this.Width - nButtonWidth, 0, nButtonWidth, this.Height);
using (SolidBrush brush = new SolidBrush(_dropDownButtonColour))
{
e.Graphics.FillRectangle(brush, dropDownButtonRect);
}
//Draw the dropdown button arrow
using (GraphicsPath path = new GraphicsPath())
{
Point pTopLeft = new Point()
{
X = (int)Math.Round((float)dropDownButtonRect.X + ((float)dropDownButtonRect.Width * 0.30f)),
Y = (int)Math.Round((float)dropDownButtonRect.Y + ((float)dropDownButtonRect.Height * 0.35f))
};
Point pTopRight = new Point()
{
X = (int)Math.Round((float)dropDownButtonRect.X + ((float)dropDownButtonRect.Width * 0.70f)),
Y = (int)Math.Round((float)dropDownButtonRect.Y + ((float)dropDownButtonRect.Height * 0.35f))
};
Point pBottom = new Point()
{
X = (int)Math.Round((float)dropDownButtonRect.X + ((float)dropDownButtonRect.Width * 0.5f)),
Y = (int)Math.Round((float)dropDownButtonRect.Y + ((float)dropDownButtonRect.Height * 0.65f))
};
path.AddLine(pTopLeft, pTopRight);
path.AddLine(pTopRight, pBottom);
SmoothingMode previousSmoothingMode = e.Graphics.SmoothingMode;
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
{
using (SolidBrush brush = new SolidBrush(_bDroppedDown ? _droppedDownArrowColour : _closedArrowColour))
{
e.Graphics.FillPath(brush, path);
}
}
e.Graphics.SmoothingMode = previousSmoothingMode;
}
if (_selectedItem != null)
{
using (SolidBrush brush = new SolidBrush(this.ForeColor))
{
SizeF stringSize = e.Graphics.MeasureString(_selectedItem.Text, this.Font);
e.Graphics.DrawString(_selectedItem.Text, this.Font, brush, new Point(0, (this.Height / 2) - ((int)stringSize.Height / 2)));
}
}
}
protected override void OnSizeChanged(EventArgs e)
{
if (_popupControl != null)
{
_popupControl.MaximumItemSize = new Size(this.Width - 1, _popupControl.MaximumItemSize.Height);
_popupControl.Width = this.Width;
}
base.OnSizeChanged(e);
}
private void OnSelectedItemChanged(EventArgs e)
{
if (SelectedItemChanged != null)
{
SelectedItemChanged(this, e);
}
}
public void SelectFirstItem()
{
if (this.Items != null && this.Items.Count > 0)
{
_selectedItem = (CustomToolStripMenuItem)this.Items[0];
OnSelectedItemChanged(EventArgs.Empty);
}
}
public event EventHandler SelectedItemChanged;
private void PopupControl_Closed(object sender, ToolStripDropDownClosedEventArgs e)
{
this.DroppedDown = false;
this.Invalidate(true);
}
private void PopupControl_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
_selectedItem = (CustomToolStripMenuItem)e.ClickedItem;
OnSelectedItemChanged(EventArgs.Empty);
}
}
}
I created custom progress bar with image but i have problem. From 50% on progress bar are showing spaces.
Progress Bar here: http://i.stack.imgur.com/d686B.png
There is control library code:
public partial class UserControl1: UserControl
{
public UserControl1() : base()
{
InitializeComponent();
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}
private void UserControl1_Load(object sender, EventArgs e)
{
}
bool _revealImage = true;
int _maximum = 100;
int _value = 0;
Image _progressbarImage = Properties.Resources.progress;
Color _progressbarColor = Color.Transparent;
protected override void OnResize(EventArgs e)
{
// Invalidate the control to get a repaint.
this.Invalidate();
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
Graphics g = e.Graphics;
Rectangle r = this.ClientRectangle;
ControlPaint.DrawBorder(g, r, Color.Transparent, ButtonBorderStyle.Solid);
float percent = (float)_value / (float)(_maximum);
//r.Inflate(-1, -1);
r.Width = (int)((float)r.Width * percent);
if ((r.Width < 1))
{
return;
}
if (_progressbarImage == null)
{
using (SolidBrush b = new SolidBrush(_progressbarColor))
{
g.FillRectangle(b, r);
b.Dispose();
}
}
else
{
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
if (_revealImage)
{
g.DrawImage(_progressbarImage, r, r, GraphicsUnit.Pixel);
}
else
{
g.DrawImage(_progressbarImage, r);
}
}
g.Dispose();
}
public int Maximum
{
get
{
return _maximum;
}
set
{
_maximum = value;
this.Invalidate();
}
}
public int Value
{
get
{
return _value;
}
set
{
// _value = value;
int oldValue = _value;
// Make sure that the value does not stray outside the valid range.
if (value < 0)
{
_value = 0;
}
else if (value > Maximum)
{
_value = Maximum;
}
else
{
_value = value;
}
// Invalidate only the changed area.
float percent;
Rectangle newValueRect = this.ClientRectangle;
Rectangle oldValueRect = this.ClientRectangle;
// Use a new value to calculate the rectangle for progress.
percent = (float)(_value - 0) / (float)(Maximum - 0);
newValueRect.Width = (int)((float)newValueRect.Width * percent);
// Use an old value to calculate the rectangle for progress.
percent = (float)(oldValue - 0) / (float)(Maximum - 0);
oldValueRect.Width = (int)((float)oldValueRect.Width * percent);
Rectangle updateRect = new Rectangle();
// Find only the part of the screen that must be updated.
if (newValueRect.Width > oldValueRect.Width)
{
updateRect.X = oldValueRect.Size.Width;
updateRect.Width = newValueRect.Width - oldValueRect.Width;
}
else
{
updateRect.X = newValueRect.Size.Width;
updateRect.Width = oldValueRect.Width - newValueRect.Width;
}
updateRect.Height = this.Height;
// Invalidate the intersection region only.
this.Invalidate(updateRect);
}
}
public Image ProgressbarImage
{
get
{
return _progressbarImage;
}
set
{
_progressbarImage = value;
this.Invalidate();
}
}
public Color ProgressbarColor
{
get
{
return _progressbarColor;
}
set
{
_progressbarColor = value;
this.Invalidate();
}
}
public bool RevealImage
{
get
{
return _revealImage;
}
set
{
_revealImage = value;
this.Invalidate();
}
}
}
Thank you for help.
I want to make an application in which the user could add rectangle with customizable text inside it. The rectangle also can have another rectangles inside. Just as you can see on these picture:
I read about DrawingVisual, Shapes etc. So far I did it using DrawingVisual + Host, which derivies from FrameworkElement. DrawingVisual has FormattedText field, and list of Children elements; Host maintain drawing all elements.
The main problem is that, everytime user changes text in any child element I need to calculate new coordinates, width, height of all child elements. Maybe there is any method to do that automatically?
Also, DrawingVisual doesn't have any mouse events. So how to make all elements selectable / hoverable? Or should I derive from some other class?
Later I will post some code...
EDIT:
public class VisualHost: FrameworkElement
{
private VisualCollection _children;
private List<MyElement> _list;
public VisualHost(List<MyElement> list)
{
_children = new VisualCollection(this);
_list = list;
}
protected override int VisualChildrenCount
{
get { return _children.Count; }
}
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index >= _children.Count)
{
throw new ArgumentOutOfRangeException();
}
return _children[index];
}
private void CheckSize(MyElement element)
{
double sw = 0;
double mh = 0;
if (element.GetChildCount() > 0)
{
for (int i = 0; i < element.GetChildCount(); i++)
{
CheckSize(element.GetChild(i));
sw += element.GetChild(i).Width;
mh = Math.Max(mh, element.GetChild(i).Height);
}
}
element.Width = Math.Max(element.Formatted.Width, sw);
element.Height = element.Formatted.Height + mh;
}
private void DrawElement(double top, double left, MyElement element)
{
CheckSize(element);
var context = element.RenderOpen();
context.DrawRectangle( null, new Pen(Brushes.Black, 2d), new Rect(new Point(left, top), new Size(element.Width, element.Height)));
context.DrawText(element.Formatted, new Point(left, top));
top += element.Formatted.Height;
if (element.GetChildCount() > 0)
{
for (int i = 0; i < element.GetChildCount(); i++)
{
context.DrawRectangle(null, new Pen(Brushes.Black, 2d), new Rect(new Point(left, top), new Size(element.GetChild(i).Width, element.GetChild(i).Height)));
context.DrawText(element.GetChild(i).Formatted, new Point(left, top));
left += element.GetChild(i).Width;
}
}
context.Close();
_children.Add(element);
}
public void Redraw()
{
if (_list != null)
{
double top = 0, left = 0;
foreach (MyElement element in _list)
{
DrawElement(top, left, element);
top += element.Height + 10d;
}
}
}
}
public class MyElement: DrawingVisual
{
private string _text;
public string Text
{
get { return _text; }
set {
if (_text != value)
{
Typeface typeface = new Typeface(new FontFamily("Arial"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
Formatted = new FormattedText(value, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 12, Brushes.Red);
_text = value;
}
}
}
public FormattedText Formatted { get; private set; }
public double Height { get; set; }
public double Width { get; set; }
private List<MyElement> _children;
public MyElement GetChild(int i)
{
if (i < 0 || i >= _children.Count)
{
throw new ArgumentOutOfRangeException();
}
return _children[i];
}
public int GetChildCount()
{
return _children.Count;
}
public void AddChild(MyElement child)
{
_children.Add(child);
}
public MyElement(string Text)
{
this.Text = Text;
this._children = new List<MyElement>();
}
}
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
_list = new List<MyElement>();
_list.Add(new MyElement("text"));
var e = new MyElement("text 2");
e.AddChild(new MyElement("a"));
e.AddChild(new MyElement("b"));
e.AddChild(new MyElement("c"));
_list.Add(e);
_host = new VisualHost(_list);
MyCanvas.Children.Add(_host);
_host.Redraw();
}
This is my code for now. I wrote it only to check if idea is correct.
well I'm not sure if you would like this approach but you can actually do it very simple... I'm thinking maybe you can use blend to create a user control and design a label and a listbox in a stackpanel and set 'em all on autosizing.
or design 2 stack panels set 1 to do vertical orientation and the other one to do horizontal and add textblocks or something to the horizontal one.
I've got a simple test app with a basic custom FrameworkElement implementation (TestElement below). The TestElement creates a couple of drawing visuals and draws some stuff in the constructor to a width of 600. It also implements the necessary bits of IScrollinfo; The Window containing the element has got a scrollviewer and a max size of 300x300. The scrollbar appears but does not scroll the content of the TestElement.
Can anyone suggest whether what I am trying to do is possible and if so what I am doing wrong. I could re-render the drawing visuals in SetHorizontalOffset but don't want to for performance reasons as I have already drawn all I need to.
I hope the question makes some sense - let me know if not and I can clarify.
Many thanks - Karl
public class TestElement : FrameworkElement, IScrollInfo
{
DrawingVisual visual;
DrawingVisual visual2;
public TestElement()
{
Draw();
this.MaxWidth = 600;
this.MaxHeight = 300;
}
public void Draw()
{
if(visual == null)
{
visual = new DrawingVisual();
base.AddVisualChild(visual);
base.AddLogicalChild(visual);
}
if (visual2 == null)
{
visual2 = new DrawingVisual();
base.AddVisualChild(visual2);
base.AddLogicalChild(visual2);
}
Random rand = new Random();
var pen = new Pen(Brushes.Black, 1);
using(var dc = visual.RenderOpen())
{
for (int i = 0; i < 400; i++)
{
var r = rand.Next(10, 200);
dc.DrawLine(pen, new Point(i, r), new Point(i, 0));
}
}
using (var dc = visual2.RenderOpen())
{
for (int i = 0; i < 200; i++)
{
var r = rand.Next(10, 200);
dc.DrawLine(pen, new Point(i, r), new Point(i, 0));
}
visual2.Offset = new Vector(400, 0);
}
}
protected override int VisualChildrenCount
{
get { return 2; }
}
protected override Visual GetVisualChild(int index)
{
return index == 0 ? visual : visual2;
}
protected override Size MeasureOverride(Size availableSize)
{
viewport = availableSize;
owner.InvalidateScrollInfo();
return base.MeasureOverride(availableSize);
}
protected override Size ArrangeOverride(Size finalSize)
{
var value = base.ArrangeOverride(finalSize);
return base.ArrangeOverride(finalSize);
}
Point offset = new Point(0,0);
public void SetHorizontalOffset(double offset)
{
this.offset.X = offset;
this.InvalidateArrange();
}
public void SetVerticalOffset(double offset)
{
this.offset.Y = offset;
}
public Rect MakeVisible(Visual visual, Rect rectangle)
{
throw new NotImplementedException();
}
public bool CanVerticallyScroll { get; set; }
public bool CanHorizontallyScroll { get; set; }
Size extent = new Size(600, 300);
private Size viewport = new Size(0, 0);
public double ExtentWidth
{
get { return extent.Width; }
}
public double ExtentHeight
{
get {return extent.Height; }
}
public double ViewportWidth
{
get { return viewport.Width; }
}
public double ViewportHeight
{
get { return viewport.Height; }
}
public double HorizontalOffset
{
get { return offset.X; }
}
public double VerticalOffset
{
get { return offset.Y; }
}
private ScrollViewer owner;
public ScrollViewer ScrollOwner
{
get { return owner; }
set { owner = value; }
}
}
the xaml:
<Window x:Class="TestWpfApp.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l ="clr-namespace:TestWpfApp"
Title="TestWpfApp" Height="300" Width="300" >
<Grid>
<ScrollViewer CanContentScroll="True" HorizontalScrollBarVisibility="Visible">
<l:TestElement CanHorizontallyScroll="True" />
</ScrollViewer>
</Grid>
Just had a beer too and it helps to find a solution. ;-)
It's not the ultimative "all well done" solution, but for sure it will help you further:
In your implementation of ArrangeOverride, try:
protected override Size ArrangeOverride(Size finalSize)
{
this.visual.Offset = new Vector(-this.HorizontalOffset, -this.VerticalOffset);
var value = base.ArrangeOverride(finalSize);
return base.ArrangeOverride(finalSize);
}
Basically you have to move your objects yourself.
For more information see this article too: iscrollinfo tutorial
.
Normally you would have to use Transformations to move the objects there where you scrolled them.