Files
ObjectListView/Rendering/Decorations.cs
2017-02-04 13:47:58 +01:00

821 lines
26 KiB
C#

/*
* Decorations - Images, text or other things that can be rendered onto an ObjectListView
*
* Author: Phillip Piper
* Date: 19/08/2009 10:56 PM
*
* Change log:
* 2011-04-04 JPP - Added ability to have a gradient background on BorderDecoration
* v2.4
* 2010-04-15 JPP - Tweaked LightBoxDecoration a little
* v2.3
* 2009-09-23 JPP - Added LeftColumn and RightColumn to RowBorderDecoration
* 2009-08-23 JPP - Added LightBoxDecoration
* 2009-08-19 JPP - Initial version. Separated from Overlays.cs
*
* To do:
*
* Copyright (C) 2009-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
*/
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Windows.Forms;
namespace BrightIdeasSoftware
{
/// <summary>
/// A decoration is an overlay that draws itself in relation to a given row or cell.
/// Decorations scroll when the listview scrolls.
/// </summary>
public interface IDecoration : IOverlay
{
/// <summary>
/// Gets or sets the row that is to be decorated
/// </summary>
OLVListItem ListItem { get; set; }
/// <summary>
/// Gets or sets the subitem that is to be decorated
/// </summary>
OLVListSubItem SubItem { get; set; }
}
/// <summary>
/// An AbstractDecoration is a safe do-nothing implementation of the IDecoration interface
/// </summary>
public class AbstractDecoration : IDecoration
{
#region IDecoration Members
/// <summary>
/// Gets or sets the row that is to be decorated
/// </summary>
public OLVListItem ListItem {
get { return listItem; }
set { listItem = value; }
}
private OLVListItem listItem;
/// <summary>
/// Gets or sets the subitem that is to be decorated
/// </summary>
public OLVListSubItem SubItem {
get { return subItem; }
set { subItem = value; }
}
private OLVListSubItem subItem;
#endregion
#region Public properties
/// <summary>
/// Gets the bounds of the decorations row
/// </summary>
public Rectangle RowBounds {
get {
if (this.ListItem == null)
return Rectangle.Empty;
else
return this.ListItem.Bounds;
}
}
/// <summary>
/// Get the bounds of the decorations cell
/// </summary>
public Rectangle CellBounds {
get {
if (this.ListItem == null || this.SubItem == null)
return Rectangle.Empty;
else
return this.ListItem.GetSubItemBounds(this.ListItem.SubItems.IndexOf(this.SubItem));
}
}
#endregion
#region IOverlay Members
/// <summary>
/// Draw the decoration
/// </summary>
/// <param name="olv"></param>
/// <param name="g"></param>
/// <param name="r"></param>
public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) {
}
#endregion
}
/// <summary>
/// This decoration draws a slight tint over a column of the
/// owning listview. If no column is explicitly set, the selected
/// column in the listview will be used.
/// The selected column is normally the sort column, but does not have to be.
/// </summary>
public class TintedColumnDecoration : AbstractDecoration
{
#region Constructors
/// <summary>
/// Create a TintedColumnDecoration
/// </summary>
public TintedColumnDecoration() {
this.Tint = Color.FromArgb(15, Color.Blue);
}
/// <summary>
/// Create a TintedColumnDecoration
/// </summary>
/// <param name="column"></param>
public TintedColumnDecoration(OLVColumn column)
: this() {
this.ColumnToTint = column;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the column that will be tinted
/// </summary>
public OLVColumn ColumnToTint {
get { return this.columnToTint; }
set { this.columnToTint = value; }
}
private OLVColumn columnToTint;
/// <summary>
/// Gets or sets the color that will be 'tinted' over the selected column
/// </summary>
public Color Tint {
get { return this.tint; }
set {
if (this.tint == value)
return;
if (this.tintBrush != null) {
this.tintBrush.Dispose();
this.tintBrush = null;
}
this.tint = value;
this.tintBrush = new SolidBrush(this.tint);
}
}
private Color tint;
private SolidBrush tintBrush;
#endregion
#region IOverlay Members
/// <summary>
/// Draw a slight colouring over our tinted column
/// </summary>
/// <remarks>
/// This overlay only works when:
/// - the list is in Details view
/// - there is at least one row
/// - there is a selected column (or a specified tint column)
/// </remarks>
/// <param name="olv"></param>
/// <param name="g"></param>
/// <param name="r"></param>
public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
if (olv.View != System.Windows.Forms.View.Details)
return;
if (olv.GetItemCount() == 0)
return;
OLVColumn column = this.ColumnToTint ?? olv.SelectedColumn;
if (column == null)
return;
Point sides = NativeMethods.GetScrolledColumnSides(olv, column.Index);
if (sides.X == -1)
return;
Rectangle columnBounds = new Rectangle(sides.X, r.Top, sides.Y - sides.X, r.Bottom);
// Find the bottom of the last item. The tinting should extend only to there.
OLVListItem lastItem = olv.GetLastItemInDisplayOrder();
if (lastItem != null) {
Rectangle lastItemBounds = lastItem.Bounds;
if (!lastItemBounds.IsEmpty && lastItemBounds.Bottom < columnBounds.Bottom)
columnBounds.Height = lastItemBounds.Bottom - columnBounds.Top;
}
g.FillRectangle(this.tintBrush, columnBounds);
}
#endregion
}
/// <summary>
/// This decoration draws an optionally filled border around a rectangle.
/// Subclasses must override CalculateBounds().
/// </summary>
public class BorderDecoration : AbstractDecoration
{
#region Constructors
/// <summary>
/// Create a BorderDecoration
/// </summary>
public BorderDecoration()
: this(new Pen(Color.FromArgb(64, Color.Blue), 1)) {
}
/// <summary>
/// Create a BorderDecoration
/// </summary>
/// <param name="borderPen">The pen used to draw the border</param>
public BorderDecoration(Pen borderPen) {
this.BorderPen = borderPen;
}
/// <summary>
/// Create a BorderDecoration
/// </summary>
/// <param name="borderPen">The pen used to draw the border</param>
/// <param name="fill">The brush used to fill the rectangle</param>
public BorderDecoration(Pen borderPen, Brush fill) {
this.BorderPen = borderPen;
this.FillBrush = fill;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the pen that will be used to draw the border
/// </summary>
public Pen BorderPen {
get { return this.borderPen; }
set { this.borderPen = value; }
}
private Pen borderPen;
/// <summary>
/// Gets or sets the padding that will be added to the bounds of the item
/// before drawing the border and fill.
/// </summary>
public Size BoundsPadding {
get { return this.boundsPadding; }
set { this.boundsPadding = value; }
}
private Size boundsPadding = new Size(-1, 2);
/// <summary>
/// How rounded should the corners of the border be? 0 means no rounding.
/// </summary>
/// <remarks>If this value is too large, the edges of the border will appear odd.</remarks>
public float CornerRounding {
get { return this.cornerRounding; }
set { this.cornerRounding = value; }
}
private float cornerRounding = 16.0f;
/// <summary>
/// Gets or sets the brush that will be used to fill the border
/// </summary>
/// <remarks>This value is ignored when using gradient brush</remarks>
public Brush FillBrush {
get { return this.fillBrush; }
set { this.fillBrush = value; }
}
private Brush fillBrush = new SolidBrush(Color.FromArgb(64, Color.Blue));
/// <summary>
/// Gets or sets the color that will be used as the start of a gradient fill.
/// </summary>
/// <remarks>This and FillGradientTo must be given value to show a gradient</remarks>
public Color? FillGradientFrom {
get { return this.fillGradientFrom; }
set { this.fillGradientFrom = value; }
}
private Color? fillGradientFrom;
/// <summary>
/// Gets or sets the color that will be used as the end of a gradient fill.
/// </summary>
/// <remarks>This and FillGradientFrom must be given value to show a gradient</remarks>
public Color? FillGradientTo {
get { return this.fillGradientTo; }
set { this.fillGradientTo = value; }
}
private Color? fillGradientTo;
/// <summary>
/// Gets or sets the fill mode that will be used for the gradient.
/// </summary>
public LinearGradientMode FillGradientMode {
get { return this.fillGradientMode; }
set { this.fillGradientMode = value; }
}
private LinearGradientMode fillGradientMode = LinearGradientMode.Vertical;
#endregion
#region IOverlay Members
/// <summary>
/// Draw a filled border
/// </summary>
/// <param name="olv"></param>
/// <param name="g"></param>
/// <param name="r"></param>
public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
Rectangle bounds = this.CalculateBounds();
if (!bounds.IsEmpty)
this.DrawFilledBorder(g, bounds);
}
#endregion
#region Subclass responsibility
/// <summary>
/// Subclasses should override this to say where the border should be drawn
/// </summary>
/// <returns></returns>
protected virtual Rectangle CalculateBounds() {
return Rectangle.Empty;
}
#endregion
#region Implementation utlities
/// <summary>
/// Do the actual work of drawing the filled border
/// </summary>
/// <param name="g"></param>
/// <param name="bounds"></param>
protected void DrawFilledBorder(Graphics g, Rectangle bounds) {
bounds.Inflate(this.BoundsPadding);
GraphicsPath path = this.GetRoundedRect(bounds, this.CornerRounding);
if (this.FillGradientFrom != null && this.FillGradientTo != null) {
if (this.FillBrush != null)
this.FillBrush.Dispose();
this.FillBrush = new LinearGradientBrush(bounds, this.FillGradientFrom.Value, this.FillGradientTo.Value, this.FillGradientMode);
}
if (this.FillBrush != null)
g.FillPath(this.FillBrush, path);
if (this.BorderPen != null)
g.DrawPath(this.BorderPen, path);
}
/// <summary>
/// Create a GraphicsPath that represents a round cornered rectangle.
/// </summary>
/// <param name="rect"></param>
/// <param name="diameter">If this is 0 or less, the rectangle will not be rounded.</param>
/// <returns></returns>
protected GraphicsPath GetRoundedRect(RectangleF rect, float diameter) {
GraphicsPath path = new GraphicsPath();
if (diameter <= 0.0f) {
path.AddRectangle(rect);
} else {
RectangleF arc = new RectangleF(rect.X, rect.Y, diameter, diameter);
path.AddArc(arc, 180, 90);
arc.X = rect.Right - diameter;
path.AddArc(arc, 270, 90);
arc.Y = rect.Bottom - diameter;
path.AddArc(arc, 0, 90);
arc.X = rect.Left;
path.AddArc(arc, 90, 90);
path.CloseFigure();
}
return path;
}
#endregion
}
/// <summary>
/// Instances of this class draw a border around the decorated row
/// </summary>
public class RowBorderDecoration : BorderDecoration
{
/// <summary>
/// Gets or sets the index of the left most column to be used for the border
/// </summary>
public int LeftColumn {
get { return leftColumn; }
set { leftColumn = value; }
}
private int leftColumn = -1;
/// <summary>
/// Gets or sets the index of the right most column to be used for the border
/// </summary>
public int RightColumn {
get { return rightColumn; }
set { rightColumn = value; }
}
private int rightColumn = -1;
/// <summary>
/// Calculate the boundaries of the border
/// </summary>
/// <returns></returns>
protected override Rectangle CalculateBounds() {
Rectangle bounds = this.RowBounds;
if (this.ListItem == null)
return bounds;
if (this.LeftColumn >= 0) {
Rectangle leftCellBounds = this.ListItem.GetSubItemBounds(this.LeftColumn);
if (!leftCellBounds.IsEmpty) {
bounds.Width = bounds.Right - leftCellBounds.Left;
bounds.X = leftCellBounds.Left;
}
}
if (this.RightColumn >= 0) {
Rectangle rightCellBounds = this.ListItem.GetSubItemBounds(this.RightColumn);
if (!rightCellBounds.IsEmpty) {
bounds.Width = rightCellBounds.Right - bounds.Left;
}
}
return bounds;
}
}
/// <summary>
/// Instances of this class draw a border around the decorated subitem.
/// </summary>
public class CellBorderDecoration : BorderDecoration
{
/// <summary>
/// Calculate the boundaries of the border
/// </summary>
/// <returns></returns>
protected override Rectangle CalculateBounds() {
return this.CellBounds;
}
}
/// <summary>
/// This decoration puts a border around the cell being edited and
/// optionally "lightboxes" the cell (makes the rest of the control dark).
/// </summary>
public class EditingCellBorderDecoration : BorderDecoration
{
#region Life and death
/// <summary>
/// Create a EditingCellBorderDecoration
/// </summary>
public EditingCellBorderDecoration() {
this.FillBrush = null;
this.BorderPen = new Pen(Color.DarkBlue, 2);
this.CornerRounding = 8;
this.BoundsPadding = new Size(10, 8);
}
/// <summary>
/// Create a EditingCellBorderDecoration
/// </summary>
/// <param name="useLightBox">Should the decoration use a lighbox display style?</param>
public EditingCellBorderDecoration(bool useLightBox) : this()
{
this.UseLightbox = useLightbox;
}
#endregion
#region Configuration properties
/// <summary>
/// Gets or set whether the decoration should make the rest of
/// the control dark when a cell is being edited
/// </summary>
/// <remarks>If this is true, FillBrush is used to overpaint
/// the control.</remarks>
public bool UseLightbox {
get { return this.useLightbox; }
set {
if (this.useLightbox == value)
return;
this.useLightbox = value;
if (this.useLightbox) {
if (this.FillBrush == null)
this.FillBrush = new SolidBrush(Color.FromArgb(64, Color.Black));
}
}
}
private bool useLightbox;
#endregion
#region Implementation
/// <summary>
/// Draw the decoration
/// </summary>
/// <param name="olv"></param>
/// <param name="g"></param>
/// <param name="r"></param>
public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
if (!olv.IsCellEditing)
return;
Rectangle bounds = olv.CellEditor.Bounds;
if (bounds.IsEmpty)
return;
bounds.Inflate(this.BoundsPadding);
GraphicsPath path = this.GetRoundedRect(bounds, this.CornerRounding);
if (this.FillBrush != null) {
if (this.UseLightbox) {
using (Region newClip = new Region(r)) {
newClip.Exclude(path);
Region originalClip = g.Clip;
g.Clip = newClip;
g.FillRectangle(this.FillBrush, r);
g.Clip = originalClip;
}
} else {
g.FillPath(this.FillBrush, path);
}
}
if (this.BorderPen != null)
g.DrawPath(this.BorderPen, path);
}
#endregion
}
/// <summary>
/// This decoration causes everything *except* the row under the mouse to be overpainted
/// with a tint, making the row under the mouse stand out in comparison.
/// The darker and more opaque the fill color, the more obvious the
/// decorated row becomes.
/// </summary>
public class LightBoxDecoration : BorderDecoration
{
/// <summary>
/// Create a LightBoxDecoration
/// </summary>
public LightBoxDecoration() {
this.BoundsPadding = new Size(-1, 4);
this.CornerRounding = 8.0f;
this.FillBrush = new SolidBrush(Color.FromArgb(72, Color.Black));
}
/// <summary>
/// Draw a tint over everything in the ObjectListView except the
/// row under the mouse.
/// </summary>
/// <param name="olv"></param>
/// <param name="g"></param>
/// <param name="r"></param>
public override void Draw(ObjectListView olv, Graphics g, Rectangle r) {
if (!r.Contains(olv.PointToClient(Cursor.Position)))
return;
Rectangle bounds = this.RowBounds;
if (bounds.IsEmpty) {
if (olv.View == View.Tile)
g.FillRectangle(this.FillBrush, r);
return;
}
using (Region newClip = new Region(r)) {
bounds.Inflate(this.BoundsPadding);
newClip.Exclude(this.GetRoundedRect(bounds, this.CornerRounding));
Region originalClip = g.Clip;
g.Clip = newClip;
g.FillRectangle(this.FillBrush, r);
g.Clip = originalClip;
}
}
}
/// <summary>
/// Instances of this class put an Image over the row/cell that it is decorating
/// </summary>
public class ImageDecoration : ImageAdornment, IDecoration
{
#region Constructors
/// <summary>
/// Create an image decoration
/// </summary>
public ImageDecoration() {
this.Alignment = ContentAlignment.MiddleRight;
}
/// <summary>
/// Create an image decoration
/// </summary>
/// <param name="image"></param>
public ImageDecoration(Image image)
: this() {
this.Image = image;
}
/// <summary>
/// Create an image decoration
/// </summary>
/// <param name="image"></param>
/// <param name="transparency"></param>
public ImageDecoration(Image image, int transparency)
: this() {
this.Image = image;
this.Transparency = transparency;
}
/// <summary>
/// Create an image decoration
/// </summary>
/// <param name="image"></param>
/// <param name="alignment"></param>
public ImageDecoration(Image image, ContentAlignment alignment)
: this() {
this.Image = image;
this.Alignment = alignment;
}
/// <summary>
/// Create an image decoration
/// </summary>
/// <param name="image"></param>
/// <param name="transparency"></param>
/// <param name="alignment"></param>
public ImageDecoration(Image image, int transparency, ContentAlignment alignment)
: this() {
this.Image = image;
this.Transparency = transparency;
this.Alignment = alignment;
}
#endregion
#region IDecoration Members
/// <summary>
/// Gets or sets the item being decorated
/// </summary>
public OLVListItem ListItem {
get { return listItem; }
set { listItem = value; }
}
private OLVListItem listItem;
/// <summary>
/// Gets or sets the sub item being decorated
/// </summary>
public OLVListSubItem SubItem {
get { return subItem; }
set { subItem = value; }
}
private OLVListSubItem subItem;
#endregion
#region Commands
/// <summary>
/// Draw this decoration
/// </summary>
/// <param name="olv">The ObjectListView being decorated</param>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The bounds of the rendering</param>
public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) {
this.DrawImage(g, this.CalculateItemBounds(this.ListItem, this.SubItem));
}
#endregion
}
/// <summary>
/// Instances of this class draw some text over the row/cell that they are decorating
/// </summary>
public class TextDecoration : TextAdornment, IDecoration
{
#region Constructors
/// <summary>
/// Create a TextDecoration
/// </summary>
public TextDecoration() {
this.Alignment = ContentAlignment.MiddleRight;
}
/// <summary>
/// Create a TextDecoration
/// </summary>
/// <param name="text"></param>
public TextDecoration(string text)
: this() {
this.Text = text;
}
/// <summary>
/// Create a TextDecoration
/// </summary>
/// <param name="text"></param>
/// <param name="transparency"></param>
public TextDecoration(string text, int transparency)
: this() {
this.Text = text;
this.Transparency = transparency;
}
/// <summary>
/// Create a TextDecoration
/// </summary>
/// <param name="text"></param>
/// <param name="alignment"></param>
public TextDecoration(string text, ContentAlignment alignment)
: this() {
this.Text = text;
this.Alignment = alignment;
}
/// <summary>
/// Create a TextDecoration
/// </summary>
/// <param name="text"></param>
/// <param name="transparency"></param>
/// <param name="alignment"></param>
public TextDecoration(string text, int transparency, ContentAlignment alignment)
: this() {
this.Text = text;
this.Transparency = transparency;
this.Alignment = alignment;
}
#endregion
#region IDecoration Members
/// <summary>
/// Gets or sets the item being decorated
/// </summary>
public OLVListItem ListItem {
get { return listItem; }
set { listItem = value; }
}
private OLVListItem listItem;
/// <summary>
/// Gets or sets the sub item being decorated
/// </summary>
public OLVListSubItem SubItem {
get { return subItem; }
set { subItem = value; }
}
private OLVListSubItem subItem;
#endregion
#region Commands
/// <summary>
/// Draw this decoration
/// </summary>
/// <param name="olv">The ObjectListView being decorated</param>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The bounds of the rendering</param>
public virtual void Draw(ObjectListView olv, Graphics g, Rectangle r) {
this.DrawText(g, this.CalculateItemBounds(this.ListItem, this.SubItem));
}
#endregion
}
}