I have a C# WPF application using XAML and MVVM. My question is: How can I show a balloon tooltip above a text box for some invalid data entered by the user?
I want to use Microsoft's native balloon control for this. How would I implement this into my application?
Just add a reference to System.Windows.Forms and C:\Program Files\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\WindowsFormsIntegration.dll
and then:
WindowsFormsHost host =new WindowsFormsHost();
var toolTip1 = new System.Windows.Forms.ToolTip();
toolTip1.AutoPopDelay = 5000;
toolTip1.InitialDelay = 1000;
toolTip1.ReshowDelay = 500;
toolTip1.ShowAlways = true;
toolTip1.IsBalloon = true;
toolTip1.ToolTipIcon = System.Windows.Forms.ToolTipIcon.Info;
toolTip1.ToolTipTitle = "Title:";
System.Windows.Forms.TextBox tb = new System.Windows.Forms.TextBox();
tb.Text="Go!";
toolTip1.SetToolTip(tb, "My Info!");
host.Child = tb;
grid1.Children.Add(host); //a container for windowsForm textBox
and this is a sample for WinForm ToolTip Ballon in WPF:
Hope this help!
This BalloonDecorator Project is one that I am using on a current project to show help hints and error notifications. I know you could modify your error template to show this decorator, just like you could show an icon instead of the red borders. The benefit of using a decorator is you can make it look however you'd like, and won't have to depend on WinForms.
BalloonDecorator.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace MyNamespace
{
public class BalloonDecorator : Decorator
{
private static double _thickness = 0;
private static int OpeningGap = 10;
public static readonly DependencyProperty BackgroundProperty =
DependencyProperty.Register("Background", typeof (Brush), typeof (BalloonDecorator));
public static readonly DependencyProperty BorderBrushProperty =
DependencyProperty.Register("BorderBrush", typeof (Brush), typeof (BalloonDecorator));
public static readonly DependencyProperty PointerLengthProperty =
DependencyProperty.Register("PointerLength", typeof (double), typeof (BalloonDecorator),
new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure));
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof (double), typeof (BalloonDecorator),
new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure));
public Brush Background
{
get { return (Brush) GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
}
public Brush BorderBrush
{
get { return (Brush) GetValue(BorderBrushProperty); }
set { SetValue(BorderBrushProperty, value); }
}
public double PointerLength
{
get { return (double) GetValue(PointerLengthProperty); }
set { SetValue(PointerLengthProperty, value); }
}
public double CornerRadius
{
get { return (double) GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
protected override Size ArrangeOverride(Size arrangeSize)
{
UIElement child = Child;
if (child != null)
{
double pLength = PointerLength;
Rect innerRect =
Rect.Inflate(new Rect(pLength, 0, Math.Max(0, arrangeSize.Width - pLength), arrangeSize.Height),
-1 * _thickness, -1 * _thickness);
child.Arrange(innerRect);
}
return arrangeSize;
}
protected override Size MeasureOverride(Size constraint)
{
UIElement child = Child;
Size size = new Size();
if (child != null)
{
Size innerSize = new Size(Math.Max(0, constraint.Width - PointerLength), constraint.Height);
child.Measure(innerSize);
size.Width += child.DesiredSize.Width;
size.Height += child.DesiredSize.Height;
}
Size borderSize = new Size(2 * _thickness, 2 * _thickness);
size.Width += borderSize.Width + PointerLength;
size.Height += borderSize.Height;
return size;
}
protected override void OnRender(DrawingContext dc)
{
Rect rect = new Rect(0, 0, RenderSize.Width, RenderSize.Height);
dc.PushClip(new RectangleGeometry(rect));
dc.DrawGeometry(Background, new Pen(BorderBrush, _thickness), CreateBalloonGeometry(rect));
dc.Pop();
}
private StreamGeometry CreateBalloonGeometry(Rect rect)
{
double radius = Math.Min(CornerRadius, rect.Height / 2);
double pointerLength = PointerLength;
// All the points on the path
Point[] points =
{
new Point(pointerLength + radius, 0), new Point(rect.Width - radius, 0), // Top
new Point(rect.Width, radius), new Point(rect.Width, rect.Height - radius), // Right
new Point(rect.Width - radius, rect.Height), // Bottom
new Point(pointerLength + radius, rect.Height), // Bottom
new Point(pointerLength, rect.Height - radius), // Left
new Point(pointerLength, radius) // Left
};
StreamGeometry geometry = new StreamGeometry();
geometry.FillRule = FillRule.Nonzero;
using (StreamGeometryContext ctx = geometry.Open())
{
ctx.BeginFigure(points[0], true, true);
ctx.LineTo(points[1], true, false);
ctx.ArcTo(points[2], new Size(radius, radius), 0, false, SweepDirection.Clockwise, true, false);
ctx.LineTo(points[3], true, false);
ctx.ArcTo(points[4], new Size(radius, radius), 0, false, SweepDirection.Clockwise, true, false);
ctx.LineTo(points[5], true, false);
ctx.ArcTo(points[6], new Size(radius, radius), 0, false, SweepDirection.Clockwise, true, false);
// Pointer
if (pointerLength > 0)
{
ctx.LineTo(rect.BottomLeft, true, false);
ctx.LineTo(new Point(pointerLength, rect.Height - radius - OpeningGap), true, false);
}
ctx.LineTo(points[7], true, false);
ctx.ArcTo(points[0], new Size(radius, radius), 0, false, SweepDirection.Clockwise, true, false);
}
return geometry;
}
}
}
Just make sure that this class's namespace is loaded into the XAML imports (I use a namespace called "Framework"), and it is simple to use:
<Framework:BalloonDecorator Background="#FFFF6600" PointerLength="50"
CornerRadius="5" Opacity=".9" Margin="200,120,0,0"
HorizontalAlignment="Left" VerticalAlignment="Top" Visibility="{Binding UnitPriceChangedBalloonVisibility}">
<Border CornerRadius="2">
<Border CornerRadius="2">
<Button Height="Auto" Command="{Binding CloseUnitPriceChangedBalloonCommand}" Background="Transparent" BorderBrush="{x:Null}">
<TextBlock Text="Please review the price. The Units have changed."
HorizontalAlignment="Left"
VerticalAlignment="Top"
FontStyle="Italic"
TextWrapping="Wrap"
Margin="10"
/>
</Button>
</Border>
</Border>
</Framework:BalloonDecorator>
Obviously, I tie the visibility to a binding, but you could just set it to true and put this inside your Validation.ErrorTemplate.
Hope this helps!
I've been searching for a better solution than the BalloonDecorator, and ran across the http://www.hardcodet.net/projects/wpf-notifyicon project. It is using the WinAPI at the lowest level, which might give you a head start on building your own solution. It appears that at first glance it could potentially solve it, but I haven't had enough time to verify that the BalloonTip can be made to behave as you've described.
Good luck on your project!
Maybe you can host a Windows Forms control in WPF using the WindowsFormsHost type.
There is a walkthrough available on MSDN on how to do this:
Hosting a Windows Forms Composite Control in WPF
Using this technique you could perhaps use the System.Windows.Forms.ToolTip control. If you set this control's IsBalloon property to true it will display as a balloon window.
Related
I am having some trouble with a custom circular progress bar control. I am trying to overlap the two of them at the lower right corner. It has a transparent background, which obviously in WinForms is showing the background, but has no effect on each other.
Here is what I am seeing:
I have been researching on stackoverflow, and have found a few answers to people having this issue with custom picturebox controls. Most of the solutions, seem to have no effect on the circular progress bar control. Some of the solutions I have tried is.
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20;
return cp;
}
}
I also have the code on the custom control for allowing transparent backgrounds. Obviously, as I stated, this does not effect overlapping controls.
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
There is also a TransparentControl solution on stackoverflow which I saw people using. I have created the control, but either have no idea how to use it, or it doesn't work in my situation. Here is the code from that control.
public class TransparentControl : Panel
{
public bool drag = false;
public bool enab = false;
private int m_opacity = 100;
private int alpha;
public TransparentControl()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
SetStyle(ControlStyles.Opaque, true);
this.BackColor = Color.Transparent;
}
public int Opacity
{
get
{
if (m_opacity > 100)
{
m_opacity = 100;
}
else if (m_opacity < 1)
{
m_opacity = 1;
}
return this.m_opacity;
}
set
{
this.m_opacity = value;
if (this.Parent != null)
{
Parent.Invalidate(this.Bounds, true);
}
}
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle = cp.ExStyle | 0x20;
return cp;
}
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Rectangle bounds = new Rectangle(0, 0, this.Width - 1, this.Height - 1);
Color frmColor = this.Parent.BackColor;
Brush bckColor = default(Brush);
alpha = (m_opacity * 255) / 100;
if (drag)
{
Color dragBckColor = default(Color);
if (BackColor != Color.Transparent)
{
int Rb = BackColor.R * alpha / 255 + frmColor.R * (255 - alpha) / 255;
int Gb = BackColor.G * alpha / 255 + frmColor.G * (255 - alpha) / 255;
int Bb = BackColor.B * alpha / 255 + frmColor.B * (255 - alpha) / 255;
dragBckColor = Color.FromArgb(Rb, Gb, Bb);
}
else
{
dragBckColor = frmColor;
}
alpha = 255;
bckColor = new SolidBrush(Color.FromArgb(alpha, dragBckColor));
}
else
{
bckColor = new SolidBrush(Color.FromArgb(alpha, this.BackColor));
}
if (this.BackColor != Color.Transparent | drag)
{
g.FillRectangle(bckColor, bounds);
}
bckColor.Dispose();
g.Dispose();
base.OnPaint(e);
}
protected override void OnBackColorChanged(EventArgs e)
{
if (this.Parent != null)
{
Parent.Invalidate(this.Bounds, true);
}
base.OnBackColorChanged(e);
}
protected override void OnParentBackColorChanged(EventArgs e)
{
this.Invalidate();
base.OnParentBackColorChanged(e);
}
}
Any assistance would be appreciated. This has been driving me nuts for hours. Thanks :)
UPDATE 1: I tried using the following code snippet from examples posted below. This yielded the same results. I still have that blank space between the circular progress bars (as seen in the picture).
Parent.Controls.Cast<Control>()
.Where(c => Parent.Controls.GetChildIndex(c) > Parent.Controls.GetChildIndex(this))
.Where(c => c.Bounds.IntersectsWith(this.Bounds))
.OrderByDescending(c => Parent.Controls.GetChildIndex(c))
.ToList()
.ForEach(c => c.DrawToBitmap(bmp, c.Bounds));
Still stumped. :(
UPDATE 2: I tried setting the front circularprogressbar to use the back circularprogressbar as it's parent in the FormLoad. That didn't work out either. It made them transparent to each other, but cut off any part of the top circularprogressbar that wasn't within' the boundaries of the back.
var pts = this.PointToScreen(circularprogressbar1.Location);
pts = circularprogressbar2.PointToClient(pts);
circularprogressbar1.Parent = circularprogressbar2;
circularprogressbar1.Location = pts;
I'm going to give you just a couple of suggestions on how to proceed.
Start off with this bare-bones transparent control (TransparentPanel).
This class is derived from Panel. That's the first choice to make: is Panel the right control to inherit from/extend for this task? Maybe it is, maybe not.
For example, a Panel is a container. Do you need the features of a container, here? Container means a lot. It inherits ScrollableControl and has ContainerControl among its Window styles. It comes with a baggage already.
You could opt for a Label instead, it's light-weight. Or build a UserControl.
I don't think there's an absolute best choice. It depends of what this custom control is used for. You need to try it out.
Note:
To create the rotation effect with the code shown here, you need the TransparentPanel Control shown below, it won't work the same way drawing on the surface of a standard Control.
This Control generates sort of a persistence in the drawn shapes, which won't exist using another type of Control as canvas (not without tweaking it heavily, that is).
class TransparentPanel : Panel
{
internal const int WS_EX_TRANSPARENT = 0x00000020;
public TransparentPanel() => InitializeComponent();
protected void InitializeComponent()
{
this.SetStyle(ControlStyles.AllPaintingInWmPaint |
ControlStyles.Opaque |
ControlStyles.ResizeRedraw |
ControlStyles.SupportsTransparentBackColor |
ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
}
protected override CreateParams CreateParams
{
get {
var cp = base.CreateParams;
cp.ExStyle |= WS_EX_TRANSPARENT;
return cp;
}
}
}
Other notes:
Here, ControlStyles.SupportsTransparentBackColor is set explicitly. The Panel class already supports this. It's specified anyway because it gives the idea of what this custom control is for just reading at its constructor.
Also, ControlStyles.OptimizedDoubleBuffer is set to false.
This prevents the System to interfere in any way with the painting of the control. There's no caching, the Custom Control is painted new when it's Invalidated. The container Form should preferably have its DoubleBuffer property set to true, but you might want test it without, to see if there's a difference.
This Custom Control (not to be confused with a UserControl) is completely transparent. It doesn't draw its background. But you can paint anything on its surface.
Take the links posted before:
This Translucent Label (no BackGround painting, disabled DoubleDuffering)
Reza Aghaei's transparent Panel (using Opacity in a different way)
TaW's Grid Panel (Color.Transparent and DoubleBuffer)
These notes: Reasons for why a WinForms label does not want to be transparent?
4 different ways to get to the same result. Which one to choose depends on the context/destination.
A design-time advice: when you are testing a custom control functionalities, remember to always rebuild the project. It can happen that a CustomControl, droppen on a Form from the Toolbox, is not updated with the new changes when the project is run.
Also, if you add or remove properties, you need to delete the control, rebuild and drop a new one on the Form.
If you don't, there's a really good chance that your modification/addition are completely ignored and you keep on testing features that never get into play.
An example, using 2 overlapping custom controls.
(using the bare-bones custom TransparentPanel)
This is the test code used to generate these drawings:
Create a new Custom Control using the TransparentPanel class shown before:
Drop two TransparentPanel objects on a test Form
Assign to TransparentPanel1 and TransparentPanel2 the transparentPanel1_Paint and transparentPanel2_Paint event handlers.
Overlap the two transparent Panels, making sure you don't nest them by mistake.
Adapt the rest of the code (you need just a Button, here named btnRotate, assign the btnRotate_Click handler)
private System.Windows.Forms.Timer RotateTimer = null;
private float RotationAngle1 = 90F;
private float RotationAngle2 = 0F;
public bool RotateFigures = false;
public form1()
{
InitializeComponent();
RotateTimer = new Timer();
RotateTimer.Interval = 50;
RotateTimer.Enabled = false;
RotateTimer.Tick += new EventHandler(this.RotateTick);
}
protected void RotateTick(object sender, EventArgs e)
{
RotationAngle1 += 10F;
RotationAngle2 += 10F;
transparentPanel1.Invalidate();
transparentPanel2.Invalidate();
}
private void btnRotate_Click(object sender, EventArgs e)
{
RotateTimer.Enabled = !RotateTimer.Enabled;
if (RotateTimer.Enabled == false)
{
RotateFigures = false;
RotationAngle1 = 90F;
RotationAngle2 = 0F;
}
else
{
RotateFigures = true;
}
}
private void transparentPanel1_Paint(object sender, PaintEventArgs e)
{
if (!RotateFigures) return;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
e.Graphics.CompositingMode = CompositingMode.SourceOver;
Rectangle rect = transparentPanel1.ClientRectangle;
Rectangle rectInner = rect;
using (Pen transpPen = new Pen(transparentPanel1.Parent.BackColor, 10))
using (Pen penOuter = new Pen(Color.SteelBlue, 8))
using (Pen penInner = new Pen(Color.Teal, 8))
using (Matrix m1 = new Matrix())
using (Matrix m2 = new Matrix())
{
m1.RotateAt(-RotationAngle1, new PointF(rect.Width / 2, rect.Height / 2));
m2.RotateAt(RotationAngle1, new PointF(rect.Width / 2, rect.Height / 2));
rect.Inflate(-(int)penOuter.Width, -(int)penOuter.Width);
rectInner.Inflate(-(int)penOuter.Width * 3, -(int)penOuter.Width * 3);
e.Graphics.Transform = m1;
e.Graphics.DrawArc(transpPen, rect, -4, 94);
e.Graphics.DrawArc(penOuter, rect, -90, 90);
e.Graphics.ResetTransform();
e.Graphics.Transform = m2;
e.Graphics.DrawArc(transpPen, rectInner, 190, 100);
e.Graphics.DrawArc(penInner, rectInner, 180, 90);
}
}
private void transparentPanel2_Paint(object sender, PaintEventArgs e)
{
if (!RotateFigures) return;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
e.Graphics.CompositingMode = CompositingMode.SourceOver;
Rectangle rect = transparentPanel2.ClientRectangle;
Rectangle rectInner = rect;
using (Pen transpPen = new Pen(transparentPanel2.Parent.BackColor, 10))
using (Pen penOuter = new Pen(Color.Orange, 8))
using (Pen penInner = new Pen(Color.DarkRed, 8))
using (Matrix m1 = new Matrix())
using (Matrix m2 = new Matrix())
{
m1.RotateAt(RotationAngle2, new PointF(rect.Width / 2, rect.Height / 2));
m2.RotateAt(-RotationAngle2, new PointF(rect.Width / 2, rect.Height / 2));
rect.Inflate(-(int)penOuter.Width, -(int)penOuter.Width);
rectInner.Inflate(-(int)penOuter.Width * 3, -(int)penOuter.Width * 3);
e.Graphics.Transform = m1;
e.Graphics.DrawArc(transpPen, rect, -4, 94);
e.Graphics.DrawArc(penOuter, rect, 0, 90);
e.Graphics.ResetTransform();
e.Graphics.Transform = m2;
e.Graphics.DrawArc(transpPen, rectInner, 190, 100);
e.Graphics.DrawArc(penInner, rectInner, 180, 90);
}
}
I've decided to repose this question with the full code. I created a test case just demonstrating what i think is the weird behavior so that other people can run the code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
namespace Test
{
public class TestSizeVisual: FrameworkElement
{
public double MinimumXInDIPs
{
get { return (double)GetValue(MinimumXInDIPsProperty); }
set
{
SetValue(MinimumXInDIPsProperty, value);
}
}
public static readonly DependencyProperty MinimumXInDIPsProperty =
DependencyProperty.Register("MinimumXInDIPs",
typeof(double), typeof(TestSizeVisual),
new FrameworkPropertyMetadata(new double(), new PropertyChangedCallback(Redraw)));
public double ViewportWidth
{
get { return (double)GetValue(ViewportWidthProperty); }
set
{
SetValue(ViewportWidthProperty, value);
}
}
public static readonly DependencyProperty ViewportWidthProperty =
DependencyProperty.Register("ViewportWidth",
typeof(double), typeof(TestSizeVisual),
new FrameworkPropertyMetadata(new double(), FrameworkPropertyMetadataOptions.AffectsMeasure));
VisualCollection visuals;
protected override Size MeasureOverride(Size availableSize)
{
return new Size(50000000000, 50);
}
public TestSizeVisual()
{
visuals = new VisualCollection(this);
this.Loaded += new RoutedEventHandler(TestSizeVisual_Loaded);
}
void TestSizeVisual_Loaded(object sender, RoutedEventArgs e)
{
DrawSignal();
}
private static void Redraw(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TestSizeVisual sg = d as TestSizeVisual;
if (sg != null && sg.IsLoaded)
{
sg.DrawSignal();
}
}
private void DrawSignal()
{
DrawingVisual signalbox = new DrawingVisual();
using (DrawingContext dc = signalbox.RenderOpen())
{
dc.DrawRectangle(Brushes.Orange, new Pen(Brushes.Black, 2), new Rect(new Point(0, 0), new Point(1000000000, ActualHeight)));
dc.DrawRectangle(Brushes.Purple, new Pen(Brushes.Black, 2), new Rect(new Point(MinimumXInDIPs, 2), new Point(MinimumXInDIPs + 10, 6)));
dc.DrawRectangle(Brushes.Purple, new Pen(Brushes.Black, 2), new Rect(new Point(MinimumXInDIPs + ViewportWidth - 10, 2), new Point(MinimumXInDIPs + ViewportWidth, 6)));
dc.DrawLine(new Pen(Brushes.Black, 2), new Point(MinimumXInDIPs, 10),
new Point(MinimumXInDIPs + ViewportWidth, 10));
}
visuals.Add(signalbox);
InvalidateVisual();
}
protected override Visual GetVisualChild(int index)
{
return visuals[index];
}
protected override int VisualChildrenCount
{
get
{
return visuals.Count;
}
}
}
}
The xaml file looks like this:
<ScrollViewer
x:Name="scrollviewer1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible"
>
<test:TestSizeVisual
MinimumXInDIPs="{Binding ElementName=scrollviewer1, Path=HorizontalOffset}"
ViewportWidth="{Binding ElementName=scrollviewer1, Path=ViewportWidth}"
/>
</ScrollViewer>
The rectangles and lines stay centered on screen as you scroll for small sizes of the testsizevisual. However, once I alter the measureoverride to return a huge size, scrolling no longer results in correct centering of the drawings. Why does everything glitch at huge sizes? Do I need to code my own scrollviewer to accomodate larger amounts of content or is there a workaround?
So a friend finally figured this out for me. Even though i was doing calculations with doubles, WPF does drawing using direct x. Direct x is limited by floats. By checking the mantissa of the float on wiki, i found that I was exceeding the value of the mantissa for a float, thus the value was no longer accurate.
I'm trying to create a custom Panel control with my own layout engine.
I need every control that is added to my panel to be added below and to take full width (-padding), like below:
Below is my code:
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.Layout;
namespace VContainer
{
internal class VerticalFillList : Panel
{
public VerticalFillList()
{
AutoScroll = true;
MinimumSize = new Size(200, 200);
Size = new Size(200, 300);
Padding = new Padding(10);
}
private readonly VerticalFillLayout _layoutEngine = new VerticalFillLayout();
public override LayoutEngine LayoutEngine
{
get { return _layoutEngine; }
}
private int _space = 10;
public int Space
{
get { return _space; }
set
{
_space = value;
Invalidate();
}
}
}
internal class VerticalFillLayout : LayoutEngine
{
public override bool Layout(object container, LayoutEventArgs layoutEventArgs)
{
var parent = container as VerticalFillList;
Rectangle parentDisplayRectangle = parent.DisplayRectangle;
Point nextControlLocation = parentDisplayRectangle.Location;
foreach (Control c in parent.Controls)
{
if (!c.Visible)
{
continue;
}
c.Location = nextControlLocation;
c.Width = parentDisplayRectangle.Width;
nextControlLocation.Offset(0, c.Height + parent.Space);
}
return false;
}
}
}
Above code works fine, except one thing:
when I'm adding controls to my container they are added correctly (new below parent, 100% width), but when height of controls is bigger than my container height I get horizontal scrollbars, but after adding couple controls more scrollbar is removed.
Same thing happens when I want to resize my container:
How this can be fixed? I just need to remove that horizontal scrollbar.
Of course all improvements are welcome :)
I don't want to use table layout or flow layout as this one gives me exactly when I need.
I need a simple container that orders all child controls from top to bottom and stretches them horizontally so they take as much width so container horizontal scrollbar isn't needed.
Here is a working example that unfortunately, does not use your Layout Engine class. It simply relies on the OnControlAdded and OnControlRemoved methods, and anchoring and setting the AutoScrollMinSize property to specifically make sure the horizontal scrollbar never appears:
internal class VerticalPanel : Panel {
private int space = 10;
public int Space {
get { return space; }
set {
space = value;
LayoutControls();
}
}
protected override void OnControlAdded(ControlEventArgs e) {
base.OnControlAdded(e);
LayoutControls();
}
protected override void OnControlRemoved(ControlEventArgs e) {
base.OnControlRemoved(e);
LayoutControls();
}
private void LayoutControls() {
int height = space;
foreach (Control c in base.Controls) {
height += c.Height + space;
}
base.AutoScrollMinSize = new Size(0, height);
int top = base.AutoScrollPosition.Y + space;
int width = base.ClientSize.Width - (space * 2);
foreach (Control c in base.Controls) {
c.SetBounds(space, top, width, c.Height);
c.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right;
top += c.Height + space;
}
}
}
You can set the AnchorProperty at you Buttons like:
button1.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top;
So they'll be resized horizontally
I need to create a custom combo box that display shapes. I created the shapes by extending the Shape class, and implementing the DefiningGeometry function like this:
public abstract class MyShape : Shape
{
public static readonly DependencyProperty SizeProperty = DependencyProperty.Register("Size", typeof(Double), typeof(MapShape));
public static readonly DependencyProperty RotationAngleProperty = DependencyProperty.Register("RotationAngle", typeof(Double), typeof(MapShape), new PropertyMetadata(0.0d));
public double Size
{
get { return (double)this.GetValue(SizeProperty); }
set { this.SetValue(SizeProperty, value); }
}
public double RotationAngle
{
get { return (double)this.GetValue(RotationAngleProperty); }
set { this.SetValue(RotationAngleProperty, value); }
}
protected override Geometry DefiningGeometry
{
get
{ return null; }
}
}
I can extend that class and create any other shape I want. for instance, I have one that looks like an arrow:
public class Arrow : MyShape
{
public Arrow() {
}
protected override Geometry DefiningGeometry
{
get
{
double oneThird = this.Size / 3;
double twoThirds = (this.Size * 2) / 3;
double oneHalf = this.Size / 2;
Point p1 = new Point(0.0d, oneThird);
Point p2 = new Point(0.0d, twoThirds);
Point p3 = new Point(oneHalf, twoThirds);
Point p4 = new Point(oneHalf, this.Size);
Point p5 = new Point(this.Size, oneHalf);
Point p6 = new Point(oneHalf, 0);
Point p7 = new Point(oneHalf, oneThird);
List<PathSegment> segments = new List<PathSegment>(3);
segments.Add(new LineSegment(p1, true));
segments.Add(new LineSegment(p2, true));
segments.Add(new LineSegment(p3, true));
segments.Add(new LineSegment(p4, true));
segments.Add(new LineSegment(p5, true));
segments.Add(new LineSegment(p6, true));
segments.Add(new LineSegment(p7, true));
List<PathFigure> figures = new List<PathFigure>(1);
PathFigure pf = new PathFigure(p1, segments, true);
figures.Add(pf);
RotateTransform rt = new RotateTransform(this.RotationAngle);
Geometry g = new PathGeometry(figures, FillRule.EvenOdd, rt);
return g;
}
}
}
I can add this shapes on XAML or code and they work just fine.
Now, those shapes are displayed on a graphics object somewhere in my form which is no relevant. The requirement I have is for the shapes on the graphic object in the form to be changed by the client from a ComboBox. So, basically I need to display the shapes inside the combo box as well. I really don't need to use these classes I am showing here, it is just for clarification I add them to this note. But I do need to customize the combobox to show shapes in the items. One way I thought is using the ControlTemplate, Any other ideas, code, readings?
Thanks!
If I understood, what you want can be achieved by customizing the ItemTemplate property of the ComboBox.
<ComboBox ...>
<ComboBox.ItemTemplate>
<DataTemplate>
<!-- Whatever UI -->
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I'm trying to make a custom ContentControl that takes on the shape of a Polyogon with rounded corners. For some reason when I set the Clip property on the Control, nothing shows up. Any help is appreciated...
PageHost.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Controls;
namespace DtcInvoicer.Controls
{
public class PageHost:UserControl
{
#region public ImageSource Icon;
public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(ImageSource), typeof(PageHost));
public ImageSource Icon
{
get { return GetValue(IconProperty) as ImageSource; }
set { SetValue(IconProperty, value); }
}
#endregion
#region public string Title;
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(PageHost));
public string Title
{
get { return GetValue(TitleProperty).ToString(); }
set { SetValue(TitleProperty, value); }
}
#endregion
#region public double Radius;
public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof(double), typeof(PageHost));
public double Radius
{
get { return (double)GetValue(RadiusProperty); }
set
{
SetValue(RadiusProperty, value);
DoClip();
}
}
#endregion
public PageHost()
{
Loaded += new RoutedEventHandler(PageHost_Loaded);
SizeChanged += new SizeChangedEventHandler(PageHost_SizeChanged);
}
#region Event Handlers
private void PageHost_Loaded(object sender, RoutedEventArgs e)
{
DoClip();
}
private void PageHost_SizeChanged(object sender, SizeChangedEventArgs e)
{
DoClip();
}
#endregion
private void DoClip()
{
Polygon p = new Polygon()
{
Points = new PointCollection()
{
new Point(0, 0),
new Point(ActualWidth - 30, 0),
new Point(ActualWidth, 30),
new Point(ActualWidth, ActualHeight),
new Point(0, ActualHeight)
}
};
Geometry g1 = new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight), Radius, Radius);
Geometry g2 = p.RenderedGeometry;
// Clip = g1; this works, the control shows up with the rounded corners
// Clip = g2; this does not work, nothing shows up
// this is what I want to do, I want to combine the two geometries
// but this does not work either
Clip = new CombinedGeometry(GeometryCombineMode.Intersect, g1, g2);
}
}
}
HomePage.xaml
<control:PageHost x:Class="DtcInvoicer.Pages.HomePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:control="clr-namespace:DtcInvoicer.Controls"
Width="500" Height="250" Radius="20" Background="Aqua">
</control:PageHost>
Setting the clip to a RenderedGeometry fails in this case because the RenderedGeometry has not yet been actually rendered, and is thus not available. For regular geometries, use this in DoClip:
Dispatcher.BeginInvoke(DispatcherPriority.Background, new ThreadStart(delegate
{
Clip = new CombinedGeometry(GeometryCombineMode.Intersect, g1, g2);
}));
With your RenderedGeometry, you would need to add it to the visual tree somewhere and then use its Loaded event before you set the Clip region, which would be hard. Try using a regular Geometry instead of a RenderedGeometry, with the same points, such as A path geometry. Here's an example of where I draw a triangle using a PathGeometry:
double leftPoint = cellRect.Right - 12;
if (leftPoint < cellRect.Left)
leftPoint = cellRect.Left;
double topPoint = cellRect.Top + (cellRect.Height - 4.0) / 2;
if (topPoint < cellRect.Top)
topPoint = cellRect.Top;
double rightPoint = leftPoint + 7;
if (rightPoint > cellRect.Right)
rightPoint = cellRect.Right;
double bottomPoint = topPoint + 4;
if (bottomPoint > cellRect.Bottom)
bottomPoint = cellRect.Bottom;
double middlePoint = leftPoint + 3;
if (middlePoint > cellRect.Right)
middlePoint = cellRect.Right;
PathFigure figure = new PathFigure();
figure.StartPoint = new Point(middlePoint, bottomPoint);
PathFigureCollection figCollection = new PathFigureCollection();
figCollection.Add(figure);
PathSegmentCollection segCollection = new PathSegmentCollection();
LineSegment topSeg = new LineSegment();
topSeg.Point = new Point(rightPoint, topPoint);
segCollection.Add(topSeg);
LineSegment midRightSeg = new LineSegment();
midRightSeg.Point = new Point(leftPoint, topPoint);
segCollection.Add(midRightSeg);
LineSegment midLeftSeg = new LineSegment();
midLeftSeg.Point = new Point(middlePoint+1, bottomPoint);
segCollection.Add(midLeftSeg);
figure.Segments = segCollection;
PathGeometry geo = new PathGeometry();
geo.Figures = figCollection;