RaUI/Source/RaUI/UI/ObjectListView/Rendering/Renderers.cs
zilinsoft 690d2651f5 ## 📅2025-08-16 星期六更新
### rycUpdate    V1.0.2506.0401
- *.[修复]修复文件替换失败,不会失败提示的BUG。
### RaUI    V4.0.2508.1601
- *.[新增]ChromeTabControl控件支持设置SizeMode属性。
- *.[新增]HotkeyTextBox新增支持传入热键文本进行自动转换。
- *.[新增]ryQuickSQL类新增GetJsonData方法。
- *.[新增]ryQuickSQL类新增DateToTimeStamp属性,导出时自动将时间类型转为时间戳。
- *.[新增]自动更新模块新增支持版本类型,区分正式版和测试版。
- *.[改进]HotkeyTextBox控件渲染方式改成全画布渲染。
- *.[改进]TextBoxEx2开启多行模式后,空文本改成在第一行显示。
- *.[修复]修复截图功能某些情况下会报错的BUG。
- *.[修复]修复HotkeyValue类处理多功能键文本时转换错误的BUG。
- *.[修复]修复RyComboBox控件在某些情况下边框会丢失的BUG。
- *.[修复]修复Hosts类针对删除hosts规则处理出错的BUG。
- *.[修复]修复当升级文件Url无法访问时,升级模块会无限期等待的BUG。
2025-08-16 14:27:11 +08:00

4158 lines
165 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Renderers - A collection of useful renderers that are used to owner draw a cell in an ObjectListView
*
* Author: Phillip Piper
* Date: 27/09/2008 9:15 AM
*
* Change log:
* v2.9
* 2015-08-22 JPP - Allow selected row back/fore colours to be specified for each row
* 2015-06-23 JPP - Added ColumnButtonRenderer plus general support for Buttons
* 2015-06-22 JPP - Added BaseRenderer.ConfigureItem() and ConfigureSubItem() to easily allow
* other renderers to be chained for use within a primary renderer.
* - Lots of tightening of hit tests and edit rectangles
* 2015-05-15 JPP - Handle renderering an Image when that Image is returned as an aspect.
* v2.8
* 2014-09-26 JPP - Dispose of animation timer in a more robust fashion.
* 2014-05-20 JPP - Handle rendering disabled rows
* v2.7
* 2013-04-29 JPP - Fixed bug where Images were not vertically aligned
* v2.6
* 2012-10-26 JPP - Hit detection will no longer report check box hits on columns without checkboxes.
* 2012-07-13 JPP - [Breaking change] Added preferedSize parameter to IRenderer.GetEditRectangle().
* v2.5.1
* 2012-07-14 JPP - Added CellPadding to various places. Replaced DescribedTaskRenderer.CellPadding.
* 2012-07-11 JPP - Added CellVerticalAlignment to various places allow cell contents to be vertically
* aligned (rather than always being centered).
* v2.5
* 2010-08-24 JPP - CheckBoxRenderer handles hot boxes and correctly vertically centers the box.
* 2010-06-23 JPP - Major rework of HighlightTextRenderer. Now uses TextMatchFilter directly.
* Draw highlighting underneath text to improve legibility. Works with new
* TextMatchFilter capabilities.
* v2.4
* 2009-10-30 JPP - Plugged possible resource leak by using using() with CreateGraphics()
* v2.3
* 2009-09-28 JPP - Added DescribedTaskRenderer
* 2009-09-01 JPP - Correctly handle an ImageRenderer's handling of an aspect that holds
* the image to be displayed at Byte[].
* 2009-08-29 JPP - Fixed bug where some of a cell's background was not erased.
* 2009-08-15 JPP - Correctly MeasureText() using the appropriate graphic context
* - Handle translucent selection setting
* v2.2.1
* 2009-07-24 JPP - Try to honour CanWrap setting when GDI rendering text.
* 2009-07-11 JPP - Correctly calculate edit rectangle for subitems of a tree view
* (previously subitems were indented in the same way as the primary column)
* v2.2
* 2009-06-06 JPP - Tweaked text rendering so that column 0 isn't ellipsed unnecessarily.
* 2009-05-05 JPP - Added Unfocused foreground and background colors
* (thanks to Christophe Hosten)
* 2009-04-21 JPP - Fixed off-by-1 error when calculating text widths. This caused
* middle and right aligned columns to always wrap one character
* when printed using ListViewPrinter (SF#2776634).
* 2009-04-11 JPP - Correctly renderer checkboxes when RowHeight is non-standard
* 2009-04-06 JPP - Allow for item indent when calculating edit rectangle
* v2.1
* 2009-02-24 JPP - Work properly with ListViewPrinter again
* 2009-01-26 JPP - AUSTRALIA DAY (why aren't I on holidays!)
* - Major overhaul of renderers. Now uses IRenderer interface.
* - ImagesRenderer and FlagsRenderer<T> are now defunct.
* The names are retained for backward compatibility.
* 2009-01-23 JPP - Align bitmap AND text according to column alignment (previously
* only text was aligned and bitmap was always to the left).
* 2009-01-21 JPP - Changed to use TextRenderer rather than native GDI routines.
* 2009-01-20 JPP - Draw images directly from image list if possible. 30% faster!
* - Tweaked some spacings to look more like native ListView
* - Text highlight for non FullRowSelect is now the right color
* when the control doesn't have focus.
* - Commented out experimental animations. Still needs work.
* 2009-01-19 JPP - Changed to draw text using GDI routines. Looks more like
* native control this way. Set UseGdiTextRendering to false to
* revert to previous behavior.
* 2009-01-15 JPP - Draw background correctly when control is disabled
* - Render checkboxes using CheckBoxRenderer
* v2.0.1
* 2008-12-29 JPP - Render text correctly when HideSelection is true.
* 2008-12-26 JPP - BaseRenderer now works correctly in all Views
* 2008-12-23 JPP - Fixed two small bugs in BarRenderer
* v2.0
* 2008-10-26 JPP - Don't owner draw when in Design mode
* 2008-09-27 JPP - Separated from ObjectListView.cs
*
* Copyright (C) 2006-2014 Phillip Piper
*
* TO DO:
* - Hit detection on renderers doesn't change the controls standard selection behavior
*
* 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@gmail.com.
*/
using ryCommon;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Web;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
using Timer = System.Threading.Timer;
namespace BrightIdeasSoftware {
/// <summary>
/// Renderers are the mechanism used for owner drawing cells. As such, they can also handle
/// hit detection and positioning of cell editing rectangles.
/// </summary>
public interface IRenderer {
/// <summary>
/// Render the whole item within an ObjectListView. This is only used in non-Details views.
/// </summary>
/// <param name="e">The event</param>
/// <param name="g">A Graphics for rendering</param>
/// <param name="itemBounds">The bounds of the item</param>
/// <param name="rowObject">The model object to be drawn</param>
/// <returns>Return true to indicate that the event was handled and no further processing is needed.</returns>
bool RenderItem(DrawListViewItemEventArgs e, Graphics g, Rectangle itemBounds, Object rowObject);
/// <summary>
/// Render one cell within an ObjectListView when it is in Details mode.
/// </summary>
/// <param name="e">The event</param>
/// <param name="g">A Graphics for rendering</param>
/// <param name="cellBounds">The bounds of the cell</param>
/// <param name="rowObject">The model object to be drawn</param>
/// <returns>Return true to indicate that the event was handled and no further processing is needed.</returns>
bool RenderSubItem(DrawListViewSubItemEventArgs e, Graphics g, Rectangle cellBounds, Object rowObject);
/// <summary>
/// What is under the given point?
/// </summary>
/// <param name="hti"></param>
/// <param name="x">x co-ordinate</param>
/// <param name="y">y co-ordinate</param>
/// <remarks>This method should only alter HitTestLocation and/or UserData.</remarks>
void HitTest(OlvListViewHitTestInfo hti, int x, int y);
/// <summary>
/// When the value in the given cell is to be edited, where should the edit rectangle be placed?
/// </summary>
/// <param name="g"></param>
/// <param name="cellBounds"></param>
/// <param name="item"></param>
/// <param name="subItemIndex"></param>
/// <param name="preferredSize"> </param>
/// <returns></returns>
Rectangle GetEditRectangle(Graphics g, Rectangle cellBounds, OLVListItem item, int subItemIndex, Size preferredSize);
}
/// <summary>
/// Renderers that implement this interface will have the filter property updated,
/// each time the filter on the ObjectListView is updated.
/// </summary>
public interface IFilterAwareRenderer
{
/// <summary>
///
/// </summary>
IModelFilter Filter { get; set; }
}
/// <summary>
/// An AbstractRenderer is a do-nothing implementation of the IRenderer interface.
/// </summary>
[Browsable(true),
ToolboxItem(false)]
public class AbstractRenderer : Component, IRenderer {
#region IRenderer Members
/// <summary>
/// Render the whole item within an ObjectListView. This is only used in non-Details views.
/// </summary>
/// <param name="e">The event</param>
/// <param name="g">A Graphics for rendering</param>
/// <param name="itemBounds">The bounds of the item</param>
/// <param name="rowObject">The model object to be drawn</param>
/// <returns>Return true to indicate that the event was handled and no further processing is needed.</returns>
public virtual bool RenderItem(DrawListViewItemEventArgs e, Graphics g, Rectangle itemBounds, object rowObject) {
return true;
}
/// <summary>
/// Render one cell within an ObjectListView when it is in Details mode.
/// </summary>
/// <param name="e">The event</param>
/// <param name="g">A Graphics for rendering</param>
/// <param name="cellBounds">The bounds of the cell</param>
/// <param name="rowObject">The model object to be drawn</param>
/// <returns>Return true to indicate that the event was handled and no further processing is needed.</returns>
public virtual bool RenderSubItem(DrawListViewSubItemEventArgs e, Graphics g, Rectangle cellBounds, object rowObject) {
return false;
}
/// <summary>
/// What is under the given point?
/// </summary>
/// <param name="hti"></param>
/// <param name="x">x co-ordinate</param>
/// <param name="y">y co-ordinate</param>
/// <remarks>This method should only alter HitTestLocation and/or UserData.</remarks>
public virtual void HitTest(OlvListViewHitTestInfo hti, int x, int y) {}
/// <summary>
/// When the value in the given cell is to be edited, where should the edit rectangle be placed?
/// </summary>
/// <param name="g"></param>
/// <param name="cellBounds"></param>
/// <param name="item"></param>
/// <param name="subItemIndex"></param>
/// <param name="preferredSize"> </param>
/// <returns></returns>
public virtual Rectangle GetEditRectangle(Graphics g, Rectangle cellBounds, OLVListItem item, int subItemIndex, Size preferredSize) {
return cellBounds;
}
#endregion
}
/// <summary>
/// This class provides compatibility for v1 RendererDelegates
/// </summary>
[ToolboxItem(false)]
internal class Version1Renderer : AbstractRenderer {
public Version1Renderer(RenderDelegate renderDelegate) {
this.RenderDelegate = renderDelegate;
}
/// <summary>
/// The renderer delegate that this renderer wraps
/// </summary>
public RenderDelegate RenderDelegate;
#region IRenderer Members
public override bool RenderSubItem(DrawListViewSubItemEventArgs e, Graphics g, Rectangle cellBounds, object rowObject) {
if (this.RenderDelegate == null)
return base.RenderSubItem(e, g, cellBounds, rowObject);
else
return this.RenderDelegate(e, g, cellBounds, rowObject);
}
#endregion
}
/// <summary>
/// A BaseRenderer provides useful base level functionality for any custom renderer.
/// </summary>
/// <remarks>
/// <para>Subclasses will normally override the Render or OptionalRender method, and use the other
/// methods as helper functions.</para>
/// </remarks>
[Browsable(true),
ToolboxItem(true)]
public class BaseRenderer : AbstractRenderer {
internal const TextFormatFlags NormalTextFormatFlags = TextFormatFlags.NoPrefix |
TextFormatFlags.EndEllipsis |
TextFormatFlags.PreserveGraphicsTranslateTransform;
#region Configuration Properties
/// <summary>
/// Can the renderer wrap lines that do not fit completely within the cell?
/// </summary>
/// <remarks>Wrapping text doesn't work with the GDI renderer.</remarks>
[Category("Appearance"),
Description("Can the renderer wrap text that does not fit completely within the cell"),
DefaultValue(false)]
public bool CanWrap {
get { return canWrap; }
set {
canWrap = value;
if (canWrap)
this.UseGdiTextRendering = false;
}
}
private bool canWrap;
/// <summary>
/// Gets or sets how many pixels will be left blank around this cell
/// </summary>
/// <remarks>
/// <para>
/// This setting only takes effect when the control is owner drawn.
/// </para>
/// <para><see cref="ObjectListView.CellPadding"/> for more details.</para>
/// </remarks>
[Category("ObjectListView"),
Description("The number of pixels that renderer will leave empty around the edge of the cell"),
DefaultValue(null)]
public Rectangle? CellPadding {
get { return this.cellPadding; }
set { this.cellPadding = value; }
}
private Rectangle? cellPadding;
/// <summary>
/// Gets the horiztonal alignment of the column
/// </summary>
[Browsable(false)]
public HorizontalAlignment CellHorizontalAlignment
{
get { return this.Column == null ? HorizontalAlignment.Left : this.Column.TextAlign; }
}
/// <summary>
/// Gets or sets how cells drawn by this renderer will be vertically aligned.
/// </summary>
/// <remarks>
/// <para>
/// If this is not set, the value from the column or control itself will be used.
/// </para>
/// </remarks>
[Category("ObjectListView"),
Description("How will cell values be vertically aligned?"),
DefaultValue(null)]
public virtual StringAlignment? CellVerticalAlignment {
get { return this.cellVerticalAlignment; }
set { this.cellVerticalAlignment = value; }
}
private StringAlignment? cellVerticalAlignment;
/// <summary>
/// Gets the optional padding that this renderer should apply before drawing.
/// This property considers all possible sources of padding
/// </summary>
[Browsable(false)]
protected virtual Rectangle? EffectiveCellPadding {
get {
if (this.cellPadding.HasValue)
return this.cellPadding.Value;
if (this.OLVSubItem != null && this.OLVSubItem.CellPadding.HasValue)
return this.OLVSubItem.CellPadding.Value;
if (this.ListItem != null && this.ListItem.CellPadding.HasValue)
return this.ListItem.CellPadding.Value;
if (this.Column != null && this.Column.CellPadding.HasValue)
return this.Column.CellPadding.Value;
if (this.ListView != null && this.ListView.CellPadding.HasValue)
return this.ListView.CellPadding.Value;
return null;
}
}
/// <summary>
/// Gets the vertical cell alignment that should govern the rendering.
/// This property considers all possible sources.
/// </summary>
[Browsable(false)]
protected virtual StringAlignment EffectiveCellVerticalAlignment {
get {
if (this.cellVerticalAlignment.HasValue)
return this.cellVerticalAlignment.Value;
if (this.OLVSubItem != null && this.OLVSubItem.CellVerticalAlignment.HasValue)
return this.OLVSubItem.CellVerticalAlignment.Value;
if (this.ListItem != null && this.ListItem.CellVerticalAlignment.HasValue)
return this.ListItem.CellVerticalAlignment.Value;
if (this.Column != null && this.Column.CellVerticalAlignment.HasValue)
return this.Column.CellVerticalAlignment.Value;
if (this.ListView != null)
return this.ListView.CellVerticalAlignment;
return StringAlignment.Center;
}
}
/// <summary>
/// Gets or sets the image list from which keyed images will be fetched
/// </summary>
[Category("Appearance"),
Description("The image list from which keyed images will be fetched for drawing. If this is not given, the small ImageList from the ObjectListView will be used"),
DefaultValue(null)]
public ImageList ImageList {
get { return imageList; }
set { imageList = value; }
}
private ImageList imageList;
/// <summary>
/// When rendering multiple images, how many pixels should be between each image?
/// </summary>
[Category("Appearance"),
Description("When rendering multiple images, how many pixels should be between each image?"),
DefaultValue(1)]
public int Spacing {
get { return spacing; }
set { spacing = value; }
}
private int spacing = 1;
/// <summary>
/// Should text be rendered using GDI routines? This makes the text look more
/// like a native List view control.
/// </summary>
[Category("Appearance"),
Description("Should text be rendered using GDI routines?"),
DefaultValue(true)]
public virtual bool UseGdiTextRendering {
get {
// Can't use GDI routines on a GDI+ printer context
return !this.IsPrinting && useGdiTextRendering;
}
set { useGdiTextRendering = value; }
}
private bool useGdiTextRendering = true;
#endregion
#region State Properties
/// <summary>
/// Get or set the aspect of the model object that this renderer should draw
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Object Aspect {
get {
if (aspect == null)
aspect = column.GetValue(this.rowObject);
return aspect;
}
set { aspect = value; }
}
private Object aspect;
/// <summary>
/// What are the bounds of the cell that is being drawn?
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Rectangle Bounds {
get { return bounds; }
set { bounds = value; }
}
private Rectangle bounds;
/// <summary>
/// Get or set the OLVColumn that this renderer will draw
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public OLVColumn Column {
get { return column; }
set { column = value; }
}
private OLVColumn column;
/// <summary>
/// Get/set the event that caused this renderer to be called
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public DrawListViewItemEventArgs DrawItemEvent {
get { return drawItemEventArgs; }
set { drawItemEventArgs = value; }
}
private DrawListViewItemEventArgs drawItemEventArgs;
/// <summary>
/// Get/set the event that caused this renderer to be called
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public DrawListViewSubItemEventArgs Event {
get { return eventArgs; }
set { eventArgs = value; }
}
private DrawListViewSubItemEventArgs eventArgs;
/// <summary>
/// Gets or sets the font to be used for text in this cell
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Font Font {
get {
if (this.font != null || this.ListItem == null)
return this.font;
if (this.SubItem == null || this.ListItem.UseItemStyleForSubItems)
return this.ListItem.Font;
return this.SubItem.Font;
}
set { this.font = value; }
}
private Font font;
/// <summary>
/// Gets the image list from which keyed images will be fetched
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ImageList ImageListOrDefault {
get { return this.ImageList ?? this.ListView.SmallImageList; }
}
/// <summary>
/// Should this renderer fill in the background before drawing?
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool IsDrawBackground {
get { return !this.IsPrinting; }
}
/// <summary>
/// Cache whether or not our item is selected
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool IsItemSelected {
get { return isItemSelected; }
set { isItemSelected = value; }
}
private bool isItemSelected;
/// <summary>
/// Is this renderer being used on a printer context?
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool IsPrinting {
get { return isPrinting; }
set { isPrinting = value; }
}
private bool isPrinting;
/// <summary>
/// Get or set the listitem that this renderer will be drawing
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public OLVListItem ListItem {
get { return listItem; }
set { listItem = value; }
}
private OLVListItem listItem;
/// <summary>
/// Get/set the listview for which the drawing is to be done
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ObjectListView ListView {
get { return objectListView; }
set { objectListView = value; }
}
private ObjectListView objectListView;
/// <summary>
/// Get the specialized OLVSubItem that this renderer is drawing
/// </summary>
/// <remarks>This returns null for column 0.</remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public OLVListSubItem OLVSubItem {
get { return listSubItem as OLVListSubItem; }
}
/// <summary>
/// Get or set the model object that this renderer should draw
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Object RowObject {
get { return rowObject; }
set { rowObject = value; }
}
private Object rowObject;
/// <summary>
/// Get or set the list subitem that this renderer will be drawing
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public OLVListSubItem SubItem {
get { return listSubItem; }
set { listSubItem = value; }
}
private OLVListSubItem listSubItem;
/// <summary>
/// The brush that will be used to paint the text
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Brush TextBrush {
get {
if (textBrush == null)
return new SolidBrush(this.GetForegroundColor());
else
return this.textBrush;
}
set { textBrush = value; }
}
private Brush textBrush;
/// <summary>
/// Will this renderer use the custom images from the parent ObjectListView
/// to draw the checkbox images.
/// </summary>
/// <remarks>
/// <para>
/// If this is true, the renderer will use the images from the
/// StateImageList to represent checkboxes. 0 - unchecked, 1 - checked, 2 - indeterminate.
/// </para>
/// <para>If this is false (the default), then the renderer will use .NET's standard
/// CheckBoxRenderer.</para>
/// </remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool UseCustomCheckboxImages {
get { return useCustomCheckboxImages; }
set { useCustomCheckboxImages = value; }
}
private bool useCustomCheckboxImages;
private void ClearState() {
this.Event = null;
this.DrawItemEvent = null;
this.Aspect = null;
this.Font = null;
this.TextBrush = null;
}
#endregion
#region Utilities
/// <summary>
/// Align the second rectangle with the first rectangle,
/// according to the alignment of the column
/// </summary>
/// <param name="outer">The cell's bounds</param>
/// <param name="inner">The rectangle to be aligned within the bounds</param>
/// <returns>An aligned rectangle</returns>
protected virtual Rectangle AlignRectangle(Rectangle outer, Rectangle inner) {
Rectangle r = new Rectangle(outer.Location, inner.Size);
// Align horizontally depending on the column alignment
if (inner.Width < outer.Width) {
r.X = AlignHorizontally(outer, inner);
}
// Align vertically too
if (inner.Height < outer.Height) {
r.Y = AlignVertically(outer, inner);
}
return r;
}
/// <summary>
/// Calculate the left edge of the rectangle that aligns the outer rectangle with the inner one
/// according to this renderer's horizontal alignment
/// </summary>
/// <param name="outer"></param>
/// <param name="inner"></param>
/// <returns></returns>
protected int AlignHorizontally(Rectangle outer, Rectangle inner) {
HorizontalAlignment alignment = this.CellHorizontalAlignment;
switch (alignment) {
case HorizontalAlignment.Left:
return outer.Left + 1;
case HorizontalAlignment.Center:
return outer.Left + ((outer.Width - inner.Width) / 2);
case HorizontalAlignment.Right:
return outer.Right - inner.Width - 1;
default:
throw new ArgumentOutOfRangeException();
}
}
/// <summary>
/// Calculate the top of the rectangle that aligns the outer rectangle with the inner rectangle
/// according to this renders vertical alignment
/// </summary>
/// <param name="outer"></param>
/// <param name="inner"></param>
/// <returns></returns>
protected int AlignVertically(Rectangle outer, Rectangle inner) {
return AlignVertically(outer, inner.Height);
}
/// <summary>
/// Calculate the top of the rectangle that aligns the outer rectangle with a rectangle of the given height
/// according to this renderer's vertical alignment
/// </summary>
/// <param name="outer"></param>
/// <param name="innerHeight"></param>
/// <returns></returns>
protected int AlignVertically(Rectangle outer, int innerHeight) {
switch (this.EffectiveCellVerticalAlignment) {
case StringAlignment.Near:
return outer.Top + 1;
case StringAlignment.Center:
return outer.Top + ((outer.Height - innerHeight) / 2);
case StringAlignment.Far:
return outer.Bottom - innerHeight - 1;
default:
throw new ArgumentOutOfRangeException();
}
}
/// <summary>
/// Calculate the space that our rendering will occupy and then align that space
/// with the given rectangle, according to the Column alignment
/// </summary>
/// <param name="g"></param>
/// <param name="r">Pre-padded bounds of the cell</param>
/// <returns></returns>
protected virtual Rectangle CalculateAlignedRectangle(Graphics g, Rectangle r) {
if (this.Column == null)
return r;
Rectangle contentRectangle = new Rectangle(Point.Empty, this.CalculateContentSize(g, r));
return this.AlignRectangle(r, contentRectangle);
}
/// <summary>
/// Calculate the size of the content of this cell.
/// </summary>
/// <param name="g"></param>
/// <param name="r">Pre-padded bounds of the cell</param>
/// <returns>The width and height of the content</returns>
protected virtual Size CalculateContentSize(Graphics g, Rectangle r)
{
Size checkBoxSize = this.CalculatePrimaryCheckBoxSize(g);
Size imageSize = this.CalculateImageSize(g, this.GetImageSelector());
Size textSize = this.CalculateTextSize(g, this.GetText(), r.Width - (checkBoxSize.Width + imageSize.Width));
// If the combined width is greater than the whole cell, we just use the cell itself
int width = Math.Min(r.Width, checkBoxSize.Width + imageSize.Width + textSize.Width);
int componentMaxHeight = Math.Max(checkBoxSize.Height, Math.Max(imageSize.Height, textSize.Height));
int height = Math.Min(r.Height, componentMaxHeight);
return new Size(width, height);
}
/// <summary>
/// Calculate the bounds of a checkbox given the (pre-padded) cell bounds
/// </summary>
/// <param name="g"></param>
/// <param name="cellBounds">Pre-padded cell bounds</param>
/// <returns></returns>
protected Rectangle CalculateCheckBoxBounds(Graphics g, Rectangle cellBounds) {
Size checkBoxSize = this.CalculateCheckBoxSize(g);
return this.AlignRectangle(cellBounds, new Rectangle(0, 0, checkBoxSize.Width, checkBoxSize.Height));
}
/// <summary>
/// How much space will the check box for this cell occupy?
/// </summary>
/// <remarks>Only column 0 can have check boxes. Sub item checkboxes are
/// treated as images</remarks>
/// <param name="g"></param>
/// <returns></returns>
protected virtual Size CalculateCheckBoxSize(Graphics g)
{
if (UseCustomCheckboxImages && this.ListView.StateImageList != null)
return this.ListView.StateImageList.ImageSize;
return CheckBoxRenderer.GetGlyphSize(g, CheckBoxState.UncheckedNormal);
}
/// <summary>
/// How much space will the check box for this row occupy?
/// If the list doesn't have checkboxes, or this isn't the primary column,
/// this returns an empty size.
/// </summary>
/// <param name="g"></param>
/// <returns></returns>
protected virtual Size CalculatePrimaryCheckBoxSize(Graphics g) {
if (!this.ListView.CheckBoxes || !this.ColumnIsPrimary)
return Size.Empty;
Size size = this.CalculateCheckBoxSize(g);
size.Width += 6;
return size;
}
/// <summary>
/// How much horizontal space will the image of this cell occupy?
/// </summary>
/// <param name="g"></param>
/// <param name="imageSelector"></param>
/// <returns></returns>
protected virtual int CalculateImageWidth(Graphics g, object imageSelector)
{
return this.CalculateImageSize(g, imageSelector).Width + 2;
}
/// <summary>
/// How much vertical space will the image of this cell occupy?
/// </summary>
/// <param name="g"></param>
/// <param name="imageSelector"></param>
/// <returns></returns>
protected virtual int CalculateImageHeight(Graphics g, object imageSelector)
{
return this.CalculateImageSize(g, imageSelector).Height;
}
/// <summary>
/// How much space will the image of this cell occupy?
/// </summary>
/// <param name="g"></param>
/// <param name="imageSelector"></param>
/// <returns></returns>
protected virtual Size CalculateImageSize(Graphics g, object imageSelector)
{
if (imageSelector == null || imageSelector == DBNull.Value)
return Size.Empty;
// Check for the image in the image list (most common case)
ImageList il = this.ImageListOrDefault;
if (il != null)
{
int selectorAsInt = -1;
if (imageSelector is Int32)
selectorAsInt = (Int32)imageSelector;
else
{
String selectorAsString = imageSelector as String;
if (selectorAsString != null)
selectorAsInt = il.Images.IndexOfKey(selectorAsString);
}
if (selectorAsInt >= 0)
return il.ImageSize;
}
// Is the selector actually an image?
Image image = imageSelector as Image;
if (image != null)
return image.Size;
return Size.Empty;
}
/// <summary>
/// How much horizontal space will the text of this cell occupy?
/// </summary>
/// <param name="g"></param>
/// <param name="txt"></param>
/// <param name="width"></param>
/// <returns></returns>
protected virtual int CalculateTextWidth(Graphics g, string txt, int width)
{
if (String.IsNullOrEmpty(txt))
return 0;
return CalculateTextSize(g, txt, width).Width;
}
/// <summary>
/// How much space will the text of this cell occupy?
/// </summary>
/// <param name="g"></param>
/// <param name="txt"></param>
/// <param name="width"></param>
/// <returns></returns>
protected virtual Size CalculateTextSize(Graphics g, string txt, int width)
{
if (String.IsNullOrEmpty(txt))
return Size.Empty;
try
{
if (this.UseGdiTextRendering)
{
Size proposedSize = new Size(width, Int32.MaxValue);
return TextRenderer.MeasureText(g, txt, this.Font, proposedSize, NormalTextFormatFlags);
}
// Using GDI+ renderering
using (StringFormat fmt = new StringFormat())
{
fmt.Trimming = StringTrimming.EllipsisCharacter;
SizeF sizeF = g.MeasureString(txt, this.Font, width, fmt);
return new Size(1 + (int)sizeF.Width, 1 + (int)sizeF.Height);
}
}
catch { return Size.Empty; }
}
/// <summary>
/// Return the Color that is the background color for this item's cell
/// </summary>
/// <returns>The background color of the subitem</returns>
public virtual Color GetBackgroundColor() {
if (!this.ListView.Enabled)
return SystemColors.Control;
if (this.IsItemSelected && !this.ListView.UseTranslucentSelection && this.ListView.FullRowSelect)
return this.GetSelectedBackgroundColor();
if (this.SubItem == null || this.ListItem.UseItemStyleForSubItems)
return this.ListItem.BackColor;
return this.SubItem.BackColor;
}
/// <summary>
/// Return the color of the background color when the item is selected
/// </summary>
/// <returns>The background color of the subitem</returns>
public virtual Color GetSelectedBackgroundColor() {
if (this.ListView.Focused)
return this.ListItem.SelectedBackColor ?? this.ListView.SelectedBackColorOrDefault;
if (!this.ListView.HideSelection)
return this.ListView.UnfocusedSelectedBackColorOrDefault;
return this.ListItem.BackColor;
}
/// <summary>
/// Return the color to be used for text in this cell
/// </summary>
/// <returns>The text color of the subitem</returns>
public virtual Color GetForegroundColor() {
if (this.IsItemSelected &&
!this.ListView.UseTranslucentSelection &&
(this.ColumnIsPrimary || this.ListView.FullRowSelect))
return this.GetSelectedForegroundColor();
return this.SubItem == null || this.ListItem.UseItemStyleForSubItems ? this.ListItem.ForeColor : this.SubItem.ForeColor;
}
/// <summary>
/// Return the color of the foreground color when the item is selected
/// </summary>
/// <returns>The foreground color of the subitem</returns>
public virtual Color GetSelectedForegroundColor()
{
if (this.ListView.Focused)
return this.ListItem.SelectedForeColor ?? this.ListView.SelectedForeColorOrDefault;
if (!this.ListView.HideSelection)
return this.ListView.UnfocusedSelectedForeColorOrDefault;
return this.SubItem == null || this.ListItem.UseItemStyleForSubItems ? this.ListItem.ForeColor : this.SubItem.ForeColor;
}
/// <summary>
/// Return the image that should be drawn against this subitem
/// </summary>
/// <returns>An Image or null if no image should be drawn.</returns>
protected virtual Image GetImage() {
return this.GetImage(this.GetImageSelector());
}
/// <summary>
/// Return the actual image that should be drawn when keyed by the given image selector.
/// An image selector can be: <list type="bullet">
/// <item><description>an int, giving the index into the image list</description></item>
/// <item><description>a string, giving the image key into the image list</description></item>
/// <item><description>an Image, being the image itself</description></item>
/// </list>
/// </summary>
/// <param name="imageSelector">The value that indicates the image to be used</param>
/// <returns>An Image or null</returns>
protected virtual Image GetImage(Object imageSelector) {
if (imageSelector == null || imageSelector == DBNull.Value)
return null;
ImageList il = this.ImageListOrDefault;
if (il != null) {
if (imageSelector is Int32) {
Int32 index = (Int32) imageSelector;
if (index < 0 || index >= il.Images.Count)
return null;
return il.Images[index];
}
String str = imageSelector as String;
if (str != null) {
if (il.Images.ContainsKey(str))
return il.Images[str];
return null;
}
}
return imageSelector as Image;
}
/// <summary>
/// </summary>
protected virtual Object GetImageSelector() {
return this.ColumnIsPrimary ? this.ListItem.ImageSelector : this.OLVSubItem.ImageSelector;
}
/// <summary>
/// Return the string that should be drawn within this
/// </summary>
/// <returns></returns>
protected virtual string GetText() {
return this.SubItem == null ? this.ListItem.Text : this.SubItem.Text;
}
/// <summary>
/// Return the Color that is the background color for this item's text
/// </summary>
/// <returns>The background color of the subitem's text</returns>
[Obsolete("Use GetBackgroundColor() instead")]
protected virtual Color GetTextBackgroundColor() {
return Color.Red; // just so it shows up if it is used
}
#endregion
#region IRenderer members
/// <summary>
/// Render the whole item in a non-details view.
/// </summary>
/// <param name="e"></param>
/// <param name="g"></param>
/// <param name="itemBounds"></param>
/// <param name="model"></param>
/// <returns></returns>
public override bool RenderItem(DrawListViewItemEventArgs e, Graphics g, Rectangle itemBounds, object model) {
this.ConfigureItem(e, itemBounds, model);
return this.OptionalRender(g, itemBounds);
}
/// <summary>
/// Prepare this renderer to draw in response to the given event
/// </summary>
/// <param name="e"></param>
/// <param name="itemBounds"></param>
/// <param name="model"></param>
/// <remarks>Use this if you want to chain a second renderer within a primary renderer.</remarks>
public virtual void ConfigureItem(DrawListViewItemEventArgs e, Rectangle itemBounds, object model)
{
this.ClearState();
this.DrawItemEvent = e;
this.ListItem = (OLVListItem)e.Item;
this.SubItem = null;
this.ListView = (ObjectListView)this.ListItem.ListView;
this.Column = this.ListView.GetColumn(0);
this.RowObject = model;
this.Bounds = itemBounds;
this.IsItemSelected = this.ListItem.Selected && this.ListItem.Enabled;
}
/// <summary>
/// Render one cell
/// </summary>
/// <param name="e"></param>
/// <param name="g"></param>
/// <param name="cellBounds"></param>
/// <param name="model"></param>
/// <returns></returns>
public override bool RenderSubItem(DrawListViewSubItemEventArgs e, Graphics g, Rectangle cellBounds, object model) {
this.ConfigureSubItem(e, cellBounds, model);
return this.OptionalRender(g, cellBounds);
}
/// <summary>
/// Prepare this renderer to draw in response to the given event
/// </summary>
/// <param name="e"></param>
/// <param name="cellBounds"></param>
/// <param name="model"></param>
/// <remarks>Use this if you want to chain a second renderer within a primary renderer.</remarks>
public virtual void ConfigureSubItem(DrawListViewSubItemEventArgs e, Rectangle cellBounds, object model) {
this.ClearState();
this.Event = e;
this.ListItem = (OLVListItem)e.Item;
this.SubItem = (OLVListSubItem)e.SubItem;
this.ListView = (ObjectListView)this.ListItem.ListView;
this.Column = (OLVColumn)e.Header;
this.RowObject = model;
this.Bounds = cellBounds;
this.IsItemSelected = this.ListItem.Selected && this.ListItem.Enabled;
}
/// <summary>
/// Calculate which part of this cell was hit
/// </summary>
/// <param name="hti"></param>
/// <param name="x"></param>
/// <param name="y"></param>
public override void HitTest(OlvListViewHitTestInfo hti, int x, int y) {
this.ClearState();
this.ListView = hti.ListView;
this.ListItem = hti.Item;
this.SubItem = hti.SubItem;
this.Column = hti.Column;
this.RowObject = hti.RowObject;
this.IsItemSelected = this.ListItem.Selected && this.ListItem.Enabled;
if (this.SubItem == null)
this.Bounds = this.ListItem.Bounds;
else
this.Bounds = this.ListItem.GetSubItemBounds(this.Column.Index);
using (Graphics g = this.ListView.CreateGraphics()) {
this.HandleHitTest(g, hti, x, y);
}
}
/// <summary>
/// Calculate the edit rectangle
/// </summary>
/// <param name="g"></param>
/// <param name="cellBounds"></param>
/// <param name="item"></param>
/// <param name="subItemIndex"></param>
/// <param name="preferredSize"> </param>
/// <returns></returns>
public override Rectangle GetEditRectangle(Graphics g, Rectangle cellBounds, OLVListItem item, int subItemIndex, Size preferredSize) {
this.ClearState();
this.ListView = (ObjectListView) item.ListView;
this.ListItem = item;
this.SubItem = item.GetSubItem(subItemIndex);
this.Column = this.ListView.GetColumn(subItemIndex);
this.RowObject = item.RowObject;
this.IsItemSelected = this.ListItem.Selected && this.ListItem.Enabled;
this.Bounds = cellBounds;
return this.HandleGetEditRectangle(g, cellBounds, item, subItemIndex, preferredSize);
}
#endregion
#region IRenderer implementation
// Subclasses will probably want to override these methods rather than the IRenderer
// interface methods.
/// <summary>
/// Draw our data into the given rectangle using the given graphics context.
/// </summary>
/// <remarks>
/// <para>Subclasses should override this method.</para></remarks>
/// <param name="g">The graphics context that should be used for drawing</param>
/// <param name="r">The bounds of the subitem cell</param>
/// <returns>Returns whether the rendering has already taken place.
/// If this returns false, the default processing will take over.
/// </returns>
public virtual bool OptionalRender(Graphics g, Rectangle r) {
if (this.ListView.View != View.Details)
return false;
this.Render(g, r);
return true;
}
/// <summary>
/// Draw our data into the given rectangle using the given graphics context.
/// </summary>
/// <remarks>
/// <para>Subclasses should override this method if they never want
/// to fall back on the default processing</para></remarks>
/// <param name="g">The graphics context that should be used for drawing</param>
/// <param name="r">The bounds of the subitem cell</param>
public virtual void Render(Graphics g, Rectangle r) {
this.StandardRender(g, r);
}
/// <summary>
/// Do the actual work of hit testing. Subclasses should override this rather than HitTest()
/// </summary>
/// <param name="g"></param>
/// <param name="hti"></param>
/// <param name="x"></param>
/// <param name="y"></param>
protected virtual void HandleHitTest(Graphics g, OlvListViewHitTestInfo hti, int x, int y) {
Rectangle r = this.CalculateAlignedRectangle(g, ApplyCellPadding(this.Bounds));
this.StandardHitTest(g, hti, r, x, y);
}
/// <summary>
/// Handle a HitTest request after all state information has been initialized
/// </summary>
/// <param name="g"></param>
/// <param name="cellBounds"></param>
/// <param name="item"></param>
/// <param name="subItemIndex"></param>
/// <param name="preferredSize"> </param>
/// <returns></returns>
protected virtual Rectangle HandleGetEditRectangle(Graphics g, Rectangle cellBounds, OLVListItem item, int subItemIndex, Size preferredSize) {
// MAINTAINER NOTE: This type testing is wrong (design-wise). The base class should return cell bounds,
// and a more specialized class should return StandardGetEditRectangle(). But BaseRenderer is used directly
// to draw most normal cells, as well as being directly subclassed for user implemented renderers. And this
// method needs to return different bounds in each of those cases. We should have a StandardRenderer and make
// BaseRenderer into an ABC -- but that would break too much existing code. And so we have this hack :(
// If we are a standard renderer, return the position of the text, otherwise, use the whole cell.
if (this.GetType() == typeof (BaseRenderer))
return this.StandardGetEditRectangle(g, cellBounds, preferredSize);
// Center the editor vertically
if (cellBounds.Height != preferredSize.Height)
cellBounds.Y += (cellBounds.Height - preferredSize.Height) / 2;
return cellBounds;
}
#endregion
#region Standard IRenderer implementations
/// <summary>
/// Draw the standard "[checkbox] [image] [text]" cell after the state properties have been initialized.
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
protected void StandardRender(Graphics g, Rectangle r) {
this.DrawBackground(g, r);
// Adjust the first columns rectangle to match the padding used by the native mode of the ListView
if (this.ColumnIsPrimary && this.CellHorizontalAlignment == HorizontalAlignment.Left ) {
r.X += 3;
r.Width -= 1;
}
r = this.ApplyCellPadding(r);
this.DrawAlignedImageAndText(g, r);
// Show where the bounds of the cell padding are (debugging)
if (ObjectListView.ShowCellPaddingBounds)
g.DrawRectangle(Pens.Purple, r);
}
/// <summary>
/// Change the bounds of the given rectangle to take any cell padding into account
/// </summary>
/// <param name="r"></param>
/// <returns></returns>
public virtual Rectangle ApplyCellPadding(Rectangle r) {
Rectangle? padding = this.EffectiveCellPadding;
if (!padding.HasValue)
return r;
// The two subtractions below look wrong, but are correct!
Rectangle paddingRectangle = padding.Value;
r.Width -= paddingRectangle.Right;
r.Height -= paddingRectangle.Bottom;
r.Offset(paddingRectangle.Location);
return r;
}
/// <summary>
/// Perform normal hit testing relative to the given aligned content bounds
/// </summary>
/// <param name="g"></param>
/// <param name="hti"></param>
/// <param name="alignedContentRectangle"></param>
/// <param name="x"></param>
/// <param name="y"></param>
protected virtual void StandardHitTest(Graphics g, OlvListViewHitTestInfo hti, Rectangle alignedContentRectangle, int x, int y) {
Rectangle r = alignedContentRectangle;
// Match tweaking from renderer
if (this.ColumnIsPrimary && this.CellHorizontalAlignment == HorizontalAlignment.Left && !(this is TreeListView.TreeRenderer)) {
r.X += 3;
r.Width -= 1;
}
int width = 0;
// Did they hit a check box on the primary column?
if (this.ColumnIsPrimary && this.ListView.CheckBoxes) {
Size checkBoxSize = this.CalculateCheckBoxSize(g);
int checkBoxTop = this.AlignVertically(r, checkBoxSize.Height);
Rectangle r3 = new Rectangle(r.X, checkBoxTop, checkBoxSize.Width, checkBoxSize.Height);
width = r3.Width + 6;
// g.DrawRectangle(Pens.DarkGreen, r3);
if (r3.Contains(x, y)) {
hti.HitTestLocation = HitTestLocation.CheckBox;
return;
}
}
// Did they hit the image? If they hit the image of a
// non-primary column that has a checkbox, it counts as a
// checkbox hit
r.X += width;
r.Width -= width;
width = this.CalculateImageWidth(g, this.GetImageSelector());
Rectangle rTwo = r;
rTwo.Width = width;
// g.DrawRectangle(Pens.Red, rTwo);
if (rTwo.Contains(x, y)) {
if (this.Column != null && (this.Column.Index > 0 && this.Column.CheckBoxes))
hti.HitTestLocation = HitTestLocation.CheckBox;
else
hti.HitTestLocation = HitTestLocation.Image;
return;
}
// Did they hit the text?
r.X += width;
r.Width -= width;
width = this.CalculateTextWidth(g, this.GetText(), r.Width);
rTwo = r;
rTwo.Width = width;
// g.DrawRectangle(Pens.Blue, rTwo);
if (rTwo.Contains(x, y)) {
hti.HitTestLocation = HitTestLocation.Text;
return;
}
hti.HitTestLocation = HitTestLocation.InCell;
}
/// <summary>
/// This method calculates the bounds of the text within a standard layout
/// (i.e. optional checkbox, optional image, text)
/// </summary>
/// <remarks>This method only works correctly if the state of the renderer
/// has been fully initialized (see BaseRenderer.GetEditRectangle)</remarks>
/// <param name="g"></param>
/// <param name="cellBounds"></param>
/// <param name="preferredSize"> </param>
/// <returns></returns>
protected virtual Rectangle StandardGetEditRectangle(Graphics g, Rectangle cellBounds, Size preferredSize) {
Size contentSize = this.CalculateContentSize(g, cellBounds);
int contentWidth = this.Column.CellEditUseWholeCellEffective ? cellBounds.Width : contentSize.Width;
Rectangle editControlBounds = this.CalculatePaddedAlignedBounds(g, cellBounds, new Size(contentWidth, preferredSize.Height));
Size checkBoxSize = this.CalculatePrimaryCheckBoxSize(g);
int imageWidth = this.CalculateImageWidth(g, this.GetImageSelector());
int width = checkBoxSize.Width + imageWidth;
// Indent the primary column by the required amount
if (this.ListItem.IndentCount > 0) {
int indentWidth = this.ListView.SmallImageSize.Width * this.ListItem.IndentCount;
width += indentWidth;
}
editControlBounds.X += width;
editControlBounds.Width -= width;
if (editControlBounds.Width < 50)
editControlBounds.Width = 50;
if (editControlBounds.Right > cellBounds.Right)
editControlBounds.Width = cellBounds.Right - editControlBounds.Left;
return editControlBounds;
}
/// <summary>
/// Apply any padding to the given bounds, and then align a rectangle of the given
/// size within that padded area.
/// </summary>
/// <param name="g"></param>
/// <param name="cellBounds"></param>
/// <param name="preferredSize"></param>
/// <returns></returns>
protected Rectangle CalculatePaddedAlignedBounds(Graphics g, Rectangle cellBounds, Size preferredSize) {
Rectangle r = ApplyCellPadding(cellBounds);
r = this.AlignRectangle(r, new Rectangle(Point.Empty, preferredSize));
return r;
}
#endregion
#region Drawing routines
/// <summary>
/// Draw the given image aligned horizontally within the column.
/// </summary>
/// <remarks>
/// Over tall images are scaled to fit. Over-wide images are
/// truncated. This is by design!
/// </remarks>
/// <param name="g">Graphics context to use for drawing</param>
/// <param name="r">Bounds of the cell</param>
/// <param name="image">The image to be drawn</param>
protected virtual void DrawAlignedImage(Graphics g, Rectangle r, Image image) {
if (image == null)
return;
// By default, the image goes in the top left of the rectangle
Rectangle imageBounds = new Rectangle(r.Location, image.Size);
// If the image is too tall to be drawn in the space provided, proportionally scale it down.
// Too wide images are not scaled.
if (image.Height > r.Height) {
float scaleRatio = (float) r.Height / (float) image.Height;
imageBounds.Width = (int) ((float) image.Width * scaleRatio);
imageBounds.Height = r.Height - 1;
}
// Align and draw our (possibly scaled) image
Rectangle alignRectangle = this.AlignRectangle(r, imageBounds);
if (this.ListItem.Enabled)
g.DrawImage(image, alignRectangle);
else
ControlPaint.DrawImageDisabled(g, image, alignRectangle.X, alignRectangle.Y, GetBackgroundColor());
}
/// <summary>
/// Draw our subitems image and text
/// </summary>
/// <param name="g">Graphics context to use for drawing</param>
/// <param name="r">Pre-padded bounds of the cell</param>
protected virtual void DrawAlignedImageAndText(Graphics g, Rectangle r) {
this.DrawImageAndText(g, this.CalculateAlignedRectangle(g, r));
}
/// <summary>
/// Fill in the background of this cell
/// </summary>
/// <param name="g">Graphics context to use for drawing</param>
/// <param name="r">Bounds of the cell</param>
protected virtual void DrawBackground(Graphics g, Rectangle r) {
if (!this.IsDrawBackground)
return;
Color backgroundColor = this.GetBackgroundColor();
using (Brush brush = new SolidBrush(backgroundColor)) {
g.FillRectangle(brush, r.X - 1, r.Y - 1, r.Width + 2, r.Height + 2);
}
}
/// <summary>
/// Draw the primary check box of this row (checkboxes in other sub items use a different method)
/// </summary>
/// <param name="g">Graphics context to use for drawing</param>
/// <param name="r">The pre-aligned and padded target rectangle</param>
protected virtual int DrawCheckBox(Graphics g, Rectangle r) {
// The odd constants are to match checkbox placement in native mode (on XP at least)
// TODO: Unify this with CheckStateRenderer
// The rectangle r is already horizontally aligned. We still need to align it vertically.
Size checkBoxSize = this.CalculateCheckBoxSize(g);
Point checkBoxLocation = new Point(r.X, this.AlignVertically(r, checkBoxSize.Height));
if (this.IsPrinting || this.UseCustomCheckboxImages) {
int imageIndex = this.ListItem.StateImageIndex;
if (this.ListView.StateImageList == null || imageIndex < 0 || imageIndex >= this.ListView.StateImageList.Images.Count)
return 0;
return this.DrawImage(g, new Rectangle(checkBoxLocation, checkBoxSize), this.ListView.StateImageList.Images[imageIndex]) + 4;
}
CheckBoxState boxState = this.GetCheckBoxState(this.ListItem.CheckState);
CheckBoxRenderer.DrawCheckBox(g, checkBoxLocation, boxState);
return checkBoxSize.Width;
}
/// <summary>
/// Calculate the CheckBoxState we need to correctly draw the given state
/// </summary>
/// <param name="checkState"></param>
/// <returns></returns>
protected virtual CheckBoxState GetCheckBoxState(CheckState checkState) {
// Should the checkbox be drawn as disabled?
if (this.IsCheckBoxDisabled) {
switch (checkState) {
case CheckState.Checked:
return CheckBoxState.CheckedDisabled;
case CheckState.Unchecked:
return CheckBoxState.UncheckedDisabled;
default:
return CheckBoxState.MixedDisabled;
}
}
// Is the cursor currently over this checkbox?
if (this.IsCheckboxHot) {
switch (checkState) {
case CheckState.Checked:
return CheckBoxState.CheckedHot;
case CheckState.Unchecked:
return CheckBoxState.UncheckedHot;
default:
return CheckBoxState.MixedHot;
}
}
// Not hot and not disabled -- just draw it normally
switch (checkState) {
case CheckState.Checked:
return CheckBoxState.CheckedNormal;
case CheckState.Unchecked:
return CheckBoxState.UncheckedNormal;
default:
return CheckBoxState.MixedNormal;
}
}
/// <summary>
/// Should this checkbox be drawn as disabled?
/// </summary>
protected virtual bool IsCheckBoxDisabled {
get {
if (this.ListItem != null && !this.ListItem.Enabled)
return true;
if (!this.ListView.RenderNonEditableCheckboxesAsDisabled)
return false;
return (this.ListView.CellEditActivation == ObjectListView.CellEditActivateMode.None ||
(this.Column != null && !this.Column.IsEditable));
}
}
/// <summary>
/// Is the current item hot (i.e. under the mouse)?
/// </summary>
protected bool IsCellHot {
get {
return this.ListView != null &&
this.ListView.HotRowIndex == this.ListItem.Index &&
this.ListView.HotColumnIndex == (this.Column == null ? 0 : this.Column.Index);
}
}
/// <summary>
/// Is the mouse over a checkbox in this cell?
/// </summary>
protected bool IsCheckboxHot {
get {
return this.IsCellHot && this.ListView.HotCellHitLocation == HitTestLocation.CheckBox;
}
}
/// <summary>
/// Draw the given text and optional image in the "normal" fashion
/// </summary>
/// <param name="g">Graphics context to use for drawing</param>
/// <param name="r">Bounds of the cell</param>
/// <param name="imageSelector">The optional image to be drawn</param>
protected virtual int DrawImage(Graphics g, Rectangle r, Object imageSelector) {
if (imageSelector == null || imageSelector == DBNull.Value)
return 0;
// Draw from the image list (most common case)
ImageList il = this.ImageListOrDefault;
if (il != null) {
// Try to translate our imageSelector into a valid ImageList index
int selectorAsInt = -1;
if (imageSelector is Int32) {
selectorAsInt = (Int32) imageSelector;
if (selectorAsInt >= il.Images.Count)
selectorAsInt = -1;
} else {
String selectorAsString = imageSelector as String;
if (selectorAsString != null)
selectorAsInt = il.Images.IndexOfKey(selectorAsString);
}
// If we found a valid index into the ImageList, draw it.
// We want to draw using the native DrawImageList calls, since that let's us do some nice effects
// But the native call does not work on PrinterDCs, so if we're printing we have to skip this bit.
if (selectorAsInt >= 0) {
if (!this.IsPrinting) {
if (il.ImageSize.Height < r.Height)
r.Y = this.AlignVertically(r, new Rectangle(Point.Empty, il.ImageSize));
// If we are not printing, it's probable that the given Graphics object is double buffered using a BufferedGraphics object.
// But the ImageList.Draw method doesn't honor the Translation matrix that's probably in effect on the buffered
// graphics. So we have to calculate our drawing rectangle, relative to the cells natural boundaries.
// This effectively simulates the Translation matrix.
Rectangle r2 = new Rectangle(r.X - this.Bounds.X, r.Y - this.Bounds.Y, r.Width, r.Height);
NativeMethods.DrawImageList(g, il, selectorAsInt, r2.X, r2.Y, this.IsItemSelected, !this.ListItem.Enabled);
return il.ImageSize.Width;
}
// For some reason, printing from an image list doesn't work onto a printer context
// So get the image from the list and FALL THROUGH to the "print an image" case
imageSelector = il.Images[selectorAsInt];
}
}
// Is the selector actually an image?
Image image = imageSelector as Image;
if (image == null)
return 0; // no, give up
if (image.Size.Height < r.Height)
r.Y = this.AlignVertically(r, new Rectangle(Point.Empty, image.Size));
if (this.ListItem.Enabled)
g.DrawImageUnscaled(image, r.X, r.Y);
else
ControlPaint.DrawImageDisabled(g, image, r.X, r.Y, GetBackgroundColor());
return image.Width;
}
/// <summary>
/// Draw our subitems image and text
/// </summary>
/// <param name="g">Graphics context to use for drawing</param>
/// <param name="r">Bounds of the cell</param>
protected virtual void DrawImageAndText(Graphics g, Rectangle r) {
int offset = 0;
if (this.ListView.CheckBoxes && this.ColumnIsPrimary) {
offset = this.DrawCheckBox(g, r) + 6;
r.X += offset;
r.Width -= offset;
}
offset = this.DrawImage(g, r, this.GetImageSelector());
r.X += offset;
r.Width -= offset;
this.DrawText(g, r, this.GetText());
}
/// <summary>
/// Draw the given collection of image selectors
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="imageSelectors"></param>
protected virtual int DrawImages(Graphics g, Rectangle r, ICollection imageSelectors) {
// Collect the non-null images
List<Image> images = new List<Image>();
foreach (Object selector in imageSelectors) {
Image image = this.GetImage(selector);
if (image != null)
images.Add(image);
}
// Figure out how much space they will occupy
int width = 0;
int height = 0;
foreach (Image image in images) {
width += (image.Width + this.Spacing);
height = Math.Max(height, image.Height);
}
// Align the collection of images within the cell
Rectangle r2 = this.AlignRectangle(r, new Rectangle(0, 0, width, height));
// Finally, draw all the images in their correct location
Color backgroundColor = GetBackgroundColor();
Point pt = r2.Location;
foreach (Image image in images) {
if (this.ListItem.Enabled)
g.DrawImage(image, pt);
else
ControlPaint.DrawImageDisabled(g, image, pt.X, pt.Y, backgroundColor);
pt.X += (image.Width + this.Spacing);
}
// Return the width that the images occupy
return width;
}
/// <summary>
/// Draw the given text and optional image in the "normal" fashion
/// </summary>
/// <param name="g">Graphics context to use for drawing</param>
/// <param name="r">Bounds of the cell</param>
/// <param name="txt">The string to be drawn</param>
public virtual void DrawText(Graphics g, Rectangle r, String txt) {
if (String.IsNullOrEmpty(txt))
return;
if (this.UseGdiTextRendering)
this.DrawTextGdi(g, r, txt);
else
this.DrawTextGdiPlus(g, r, txt);
}
/// <summary>
/// Draw the given text and optional image in the "normal" fashion
/// </summary>
/// <param name="g">Graphics context to use for drawing</param>
/// <param name="r">Bounds of the cell</param>
/// <param name="offset_x"></param>
/// <param name="txt">The string to be drawn</param>
/// <param name="brush"></param>
public virtual int DrawText2(Graphics g, Rectangle r, int offset_x, String txt, Brush brush)
{
var txt_size = g.MeasureString(txt, Font).ToSize();
StringFormat sf = new StringFormat();
sf.Trimming = StringTrimming.EllipsisCharacter;
sf.FormatFlags = StringFormatFlags.NoWrap;//<2F>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD>
g.DrawString(txt, Font, brush, new RectangleF(r.Left + offset_x, r.Top + 2 + (r.Height - txt_size.Height) / 2, r.Width - offset_x, r.Height), sf);
return txt_size.Width + 1;
}
/// <summary>
/// Draw the given text and optional image in the "normal" fashion
/// </summary>
/// <param name="g">Graphics context to use for drawing</param>
/// <param name="r">Bounds of the cell</param>
/// <param name="offset_x"></param>
/// <param name="txt">The string to be drawn</param>
public virtual int DrawText2(Graphics g, Rectangle r, int offset_x, String txt)
{
return DrawText2(g, r, offset_x, txt, new SolidBrush(GetForegroundColor()));
}
/// <summary>
///
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="offset_x"></param>
/// <param name="txt"></param>
/// <param name="fore_brush"></param>
/// <param name="back_brush"></param>
/// <returns></returns>
public virtual int DrawTextAndBackColor2(Graphics g, Rectangle r, int offset_x, string txt, Brush fore_brush, Brush back_brush)
{
var txt_size = g.MeasureString(txt, Font).ToSize();
var rect = new RectangleF(r.Left + offset_x, r.Top + 2 + (r.Height - txt_size.Height) / 2, r.Width - offset_x, r.Height);
var rect_back = new RectangleF(r.Left + offset_x, r.Top + 2, txt_size.Width, r.Height - 4);
g.FillRectangle(back_brush, rect_back);
StringFormat sf = new StringFormat();
sf.Trimming = StringTrimming.EllipsisCharacter;
g.DrawString(txt, Font, fore_brush, rect, sf);
return txt_size.Width + 1;
}
/// <summary>
/// Draw the given text and optional image in the "normal" fashion
/// </summary>
/// <param name="g">Graphics context to use for drawing</param>
/// <param name="r">Bounds of the cell</param>
/// <param name="offset_x"></param>
/// <param name="txt">The string to be drawn</param>
/// <param name="forecolor">笔刷</param>
public virtual int DrawText2(Graphics g, Rectangle r,int offset_x, String txt, Color forecolor)
{
try
{
StringFormat sf = new StringFormat();
sf.Trimming = StringTrimming.EllipsisCharacter;
sf.FormatFlags = StringFormatFlags.NoWrap;//自动换行
Size txt_size = TextRenderer.MeasureText(g, txt, Font, r.Size, TextFormatFlags.NoPadding | TextFormatFlags.PreserveGraphicsTranslateTransform);
TextRenderer.DrawText(g, txt, Font, new Rectangle(r.Left + offset_x, r.Top + 2 + (r.Height - txt_size.Height) / 2, r.Width - offset_x, r.Height), forecolor, TextFormatFlags.NoPadding | TextFormatFlags.PreserveGraphicsTranslateTransform);
//g.DrawString(txt, Font, br ush,new RectangleF(r.Left+ offset_x, r.Top + 2 + (r.Height - txt_size.Height) / 2,r.Width- offset_x, r.Height),sf);
return txt_size.Width;
}
catch { return 0; }
}
/// <summary>
/// Draw the given text and optional image in the "normal" fashion
/// </summary>
/// <param name="g">Graphics context to use for drawing</param>
/// <param name="r">Bounds of the cell</param>
/// <param name="offset_x"></param>
/// <param name="txt">The string to be drawn</param>
public virtual int DrawText3(Graphics g, Rectangle r, int offset_x, String txt)
{
return DrawText2(g, r, offset_x, txt, GetForegroundColor());
}
/// <summary>
///
/// </summary>
/// <param name="g"></param>
/// <param name="r">这里的r</param>
/// <param name="offset_x"></param>
/// <param name="txt"></param>
/// <param name="forecolor"></param>
/// <param name="backcolor"></param>
/// <returns></returns>
public virtual int DrawTextAndBackColor2(Graphics g, Rectangle r, int offset_x, string txt, Color forecolor, Color backcolor)
{
try
{
var txt_size = TextRenderer.MeasureText(g, txt, Font, r.Size, TextFormatFlags.NoPadding | TextFormatFlags.PreserveGraphicsTranslateTransform);
var rect = new Rectangle(r.Left + offset_x, r.Top + 2 + (r.Height - txt_size.Height) / 2, r.Width - offset_x, r.Height);
var rect_back = new Rectangle(r.Left + offset_x, r.Top + 2, txt_size.Width, r.Height - 4);
g.FillRectangle(new SolidBrush(backcolor), rect_back);
TextRenderer.DrawText(g, txt, Font, rect, forecolor, TextFormatFlags.NoPadding | TextFormatFlags.PreserveGraphicsTranslateTransform);
return txt_size.Width;
}
catch { return 0; }
}
/// <summary>
///
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="offset_x"></param>
/// <param name="bitmap"></param>
/// <param name="bit_height"></param>
/// <returns></returns>
public virtual int DrawImage2(Graphics g, Rectangle r, int offset_x, Bitmap bitmap,int bit_height=20)
{
var width_bit = bitmap.Width * bit_height / bitmap.Height;
g.DrawImage(bitmap, r.X+ offset_x, r.Y + (r.Height - bit_height) / 2, width_bit, bit_height);
return width_bit ;
}
/// <summary>
/// Print the given text in the given rectangle using only GDI routines
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="txt"></param>
/// <remarks>
/// The native list control uses GDI routines to do its drawing, so using them
/// here makes the owner drawn mode looks more natural.
/// <para>This method doesn't honour the CanWrap setting on the renderer. All
/// text is single line</para>
/// </remarks>
protected virtual void DrawTextGdi(Graphics g, Rectangle r, String txt) {
Color backColor = Color.Transparent;
if (this.IsDrawBackground && this.IsItemSelected && ColumnIsPrimary && !this.ListView.FullRowSelect)
backColor = this.GetSelectedBackgroundColor();
TextFormatFlags flags = NormalTextFormatFlags | this.CellVerticalAlignmentAsTextFormatFlag;
// I think there is a bug in the TextRenderer. Setting or not setting SingleLine doesn't make
// any difference -- it is always single line.
if (!this.CanWrap)
flags |= TextFormatFlags.SingleLine;
TextRenderer.DrawText(g, txt, this.Font, r, this.GetForegroundColor(), backColor, flags);
}
private bool ColumnIsPrimary {
get { return this.Column != null && this.Column.Index == 0; }
}
/// <summary>
/// Gets the cell's vertical alignment as a TextFormatFlag
/// </summary>
/// <exception cref="ArgumentOutOfRangeException"></exception>
protected TextFormatFlags CellVerticalAlignmentAsTextFormatFlag {
get {
switch (this.EffectiveCellVerticalAlignment) {
case StringAlignment.Near:
return TextFormatFlags.Top;
case StringAlignment.Center:
return TextFormatFlags.VerticalCenter;
case StringAlignment.Far:
return TextFormatFlags.Bottom;
default:
throw new ArgumentOutOfRangeException();
}
}
}
/// <summary>
/// Gets the StringFormat needed when drawing text using GDI+
/// </summary>
protected virtual StringFormat StringFormatForGdiPlus {
get {
StringFormat fmt = new StringFormat();
fmt.LineAlignment = this.EffectiveCellVerticalAlignment;
fmt.Trimming = StringTrimming.EllipsisCharacter;
fmt.Alignment = this.Column == null ? StringAlignment.Near : this.Column.TextStringAlign;
if (!this.CanWrap)
fmt.FormatFlags = StringFormatFlags.NoWrap;
return fmt;
}
}
/// <summary>
/// Print the given text in the given rectangle using normal GDI+ .NET methods
/// </summary>
/// <remarks>Printing to a printer dc has to be done using this method.</remarks>
protected virtual void DrawTextGdiPlus(Graphics g, Rectangle r, String txt) {
using (StringFormat fmt = this.StringFormatForGdiPlus) {
// Draw the background of the text as selected, if it's the primary column
// and it's selected and it's not in FullRowSelect mode.
Font f = this.Font;
if (this.IsDrawBackground && this.IsItemSelected && this.ColumnIsPrimary && !this.ListView.FullRowSelect) {
SizeF size = g.MeasureString(txt, f, r.Width, fmt);
Rectangle r2 = r;
r2.Width = (int) size.Width + 1;
using (Brush brush = new SolidBrush(this.GetSelectedBackgroundColor())) {
g.FillRectangle(brush, r2);
}
}
RectangleF rf = r;
g.DrawString(txt, f, this.TextBrush, rf, fmt);
}
// We should put a focus rectangle around the column 0 text if it's selected --
// but we don't because:
// - I really dislike this UI convention
// - we are using buffered graphics, so the DrawFocusRecatangle method of the event doesn't work
//if (this.ColumnIsPrimary) {
// Size size = TextRenderer.MeasureText(this.SubItem.Text, this.ListView.ListFont);
// if (r.Width > size.Width)
// r.Width = size.Width;
// this.Event.DrawFocusRectangle(r);
//}
}
#endregion
}
/// <summary>
/// This renderer highlights substrings that match a given text filter.
/// </summary>
public class HighlightTextRenderer : BaseRenderer, IFilterAwareRenderer {
#region Life and death
/// <summary>
/// Create a HighlightTextRenderer
/// </summary>
public HighlightTextRenderer() {
this.FramePen = Pens.DarkGreen;
this.FillBrush = Brushes.Yellow;
}
/// <summary>
/// Create a HighlightTextRenderer
/// </summary>
/// <param name="filter"></param>
public HighlightTextRenderer(TextMatchFilter filter)
: this() {
this.Filter = filter;
}
/// <summary>
/// Create a HighlightTextRenderer
/// </summary>
/// <param name="text"></param>
[Obsolete("Use HighlightTextRenderer(TextMatchFilter) instead", true)]
public HighlightTextRenderer(string text) {}
#endregion
#region Configuration properties
/// <summary>
/// Gets or set how rounded will be the corners of the text match frame
/// </summary>
[Category("Appearance"),
DefaultValue(3.0f),
Description("How rounded will be the corners of the text match frame?")]
public float CornerRoundness {
get { return cornerRoundness; }
set { cornerRoundness = value; }
}
private float cornerRoundness = 3.0f;
/// <summary>
/// Gets or set the brush will be used to paint behind the matched substrings.
/// Set this to null to not fill the frame.
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Brush FillBrush {
get { return fillBrush; }
set { fillBrush = value; }
}
private Brush fillBrush;
/// <summary>
/// Gets or sets the filter that is filtering the ObjectListView and for
/// which this renderer should highlight text
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public TextMatchFilter Filter {
get { return filter; }
set { filter = value; }
}
private TextMatchFilter filter;
/// <summary>
/// When a filter changes, keep track of the text matching filters
/// </summary>
IModelFilter IFilterAwareRenderer.Filter
{
get { return filter; }
set { RegisterNewFilter(value); }
}
internal void RegisterNewFilter(IModelFilter newFilter) {
TextMatchFilter textFilter = newFilter as TextMatchFilter;
if (textFilter != null)
{
Filter = textFilter;
return;
}
CompositeFilter composite = newFilter as CompositeFilter;
if (composite != null)
{
foreach (TextMatchFilter textSubFilter in composite.TextFilters)
{
Filter = textSubFilter;
return;
}
}
Filter = null;
}
/// <summary>
/// Gets or set the pen will be used to frame the matched substrings.
/// Set this to null to not draw a frame.
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Pen FramePen {
get { return framePen; }
set { framePen = value; }
}
private Pen framePen;
/// <summary>
/// Gets or sets whether the frame around a text match will have rounded corners
/// </summary>
[Category("Appearance"),
DefaultValue(true),
Description("Will the frame around a text match will have rounded corners?")]
public bool UseRoundedRectangle {
get { return useRoundedRectangle; }
set { useRoundedRectangle = value; }
}
private bool useRoundedRectangle = true;
#endregion
#region Compatibility properties
/// <summary>
/// Gets or set the text that will be highlighted
/// </summary>
[Obsolete("Set the Filter directly rather than just the text", true)]
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public string TextToHighlight {
get { return String.Empty; }
set { }
}
/// <summary>
/// Gets or sets the manner in which substring will be compared.
/// </summary>
/// <remarks>
/// Use this to control if substring matches are case sensitive or insensitive.</remarks>
[Obsolete("Set the Filter directly rather than just this setting", true)]
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public StringComparison StringComparison {
get { return StringComparison.CurrentCultureIgnoreCase; }
set { }
}
#endregion
#region IRenderer interface overrides
/// <summary>
/// Handle a HitTest request after all state information has been initialized
/// </summary>
/// <param name="g"></param>
/// <param name="cellBounds"></param>
/// <param name="item"></param>
/// <param name="subItemIndex"></param>
/// <param name="preferredSize"> </param>
/// <returns></returns>
protected override Rectangle HandleGetEditRectangle(Graphics g, Rectangle cellBounds, OLVListItem item, int subItemIndex, Size preferredSize) {
return this.StandardGetEditRectangle(g, cellBounds, preferredSize);
}
#endregion
#region Rendering
// This class has two implement two highlighting schemes: one for GDI, another for GDI+.
// Naturally, GDI+ makes the task easier, but we have to provide something for GDI
// since that it is what is normally used.
/// <summary>
/// Draw text using GDI
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="txt"></param>
protected override void DrawTextGdi(Graphics g, Rectangle r, string txt) {
if (this.ShouldDrawHighlighting)
this.DrawGdiTextHighlighting(g, r, txt);
base.DrawTextGdi(g, r, txt);
}
/// <summary>
/// Draw the highlighted text using GDI
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="txt"></param>
protected virtual void DrawGdiTextHighlighting(Graphics g, Rectangle r, string txt) {
// TextRenderer puts horizontal padding around the strings, so we need to take
// that into account when measuring strings
const int paddingAdjustment = 6;
// Cache the font
Font f = this.Font;
foreach (CharacterRange range in this.Filter.FindAllMatchedRanges(txt)) {
// Measure the text that comes before our substring
Size precedingTextSize = Size.Empty;
if (range.First > 0) {
string precedingText = txt.Substring(0, range.First);
precedingTextSize = TextRenderer.MeasureText(g, precedingText, f, r.Size, NormalTextFormatFlags);
precedingTextSize.Width -= paddingAdjustment;
}
// Measure the length of our substring (may be different each time due to case differences)
string highlightText = txt.Substring(range.First, range.Length);
Size textToHighlightSize = TextRenderer.MeasureText(g, highlightText, f, r.Size, NormalTextFormatFlags);
textToHighlightSize.Width -= paddingAdjustment;
float textToHighlightLeft = r.X + precedingTextSize.Width + 1;
float textToHighlightTop = this.AlignVertically(r, textToHighlightSize.Height);
// Draw a filled frame around our substring
this.DrawSubstringFrame(g, textToHighlightLeft, textToHighlightTop, textToHighlightSize.Width, textToHighlightSize.Height);
}
}
/// <summary>
/// Draw an indication around the given frame that shows a text match
/// </summary>
/// <param name="g"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="width"></param>
/// <param name="height"></param>
protected virtual void DrawSubstringFrame(Graphics g, float x, float y, float width, float height) {
if (this.UseRoundedRectangle) {
using (GraphicsPath path = this.GetRoundedRect(x, y, width, height, 3.0f)) {
if (this.FillBrush != null)
{
if (this.IsItemSelected)
{ g.FillPath(Brushes.Red, path); }
else
{
g.FillPath(this.FillBrush, path);
}
}
if (this.FramePen != null)
g.DrawPath(this.FramePen, path);
}
} else {
if (this.FillBrush != null)
if (this.IsItemSelected)
{ g.FillRectangle(Brushes.Red, x, y, width, height); }
else
{
g.FillRectangle(this.FillBrush, x, y, width, height);
}
if (this.FramePen != null)
g.DrawRectangle(this.FramePen, x, y, width, height);
}
}
/// <summary>
/// Draw the text using GDI+
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="txt"></param>
protected override void DrawTextGdiPlus(Graphics g, Rectangle r, string txt) {
if (this.ShouldDrawHighlighting)
this.DrawGdiPlusTextHighlighting(g, r, txt);
base.DrawTextGdiPlus(g, r, txt);
}
/// <summary>
/// Draw the highlighted text using GDI+
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="txt"></param>
protected virtual void DrawGdiPlusTextHighlighting(Graphics g, Rectangle r, string txt) {
// Find the substrings we want to highlight
List<CharacterRange> ranges = new List<CharacterRange>(this.Filter.FindAllMatchedRanges(txt));
if (ranges.Count == 0)
return;
using (StringFormat fmt = this.StringFormatForGdiPlus) {
RectangleF rf = r;
fmt.SetMeasurableCharacterRanges(ranges.ToArray());
Region[] stringRegions = g.MeasureCharacterRanges(txt, this.Font, rf, fmt);
foreach (Region region in stringRegions) {
RectangleF bounds = region.GetBounds(g);
this.DrawSubstringFrame(g, bounds.X - 1, bounds.Y - 1, bounds.Width + 2, bounds.Height);
}
}
}
#endregion
#region Utilities
/// <summary>
/// Gets whether the renderer should actually draw highlighting
/// </summary>
protected bool ShouldDrawHighlighting {
get { return this.Column == null || (this.Column.Searchable && this.Filter != null && this.Filter.HasComponents); }
}
/// <summary>
/// Return a GraphicPath that is a round cornered rectangle
/// </summary>
/// <returns>A round cornered rectangle path</returns>
/// <remarks>If I could rely on people using C# 3.0+, this should be
/// an extension method of GraphicsPath.</remarks>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <param name="diameter"></param>
protected GraphicsPath GetRoundedRect(float x, float y, float width, float height, float diameter) {
return GetRoundedRect(new RectangleF(x, y, width, height), diameter);
}
/// <summary>
/// Return a GraphicPath that is a round cornered rectangle
/// </summary>
/// <param name="rect">The rectangle</param>
/// <param name="diameter">The diameter of the corners</param>
/// <returns>A round cornered rectangle path</returns>
/// <remarks>If I could rely on people using C# 3.0+, this should be
/// an extension method of GraphicsPath.</remarks>
protected GraphicsPath GetRoundedRect(RectangleF rect, float diameter) {
GraphicsPath path = new GraphicsPath();
if (diameter > 0) {
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();
} else {
path.AddRectangle(rect);
}
return path;
}
#endregion
}
/// <summary>
/// This class maps a data value to an image that should be drawn for that value.
/// </summary>
/// <remarks><para>It is useful for drawing data that is represented as an enum or boolean.</para></remarks>
public class MappedImageRenderer : BaseRenderer {
/// <summary>
/// Return a renderer that draw boolean values using the given images
/// </summary>
/// <param name="trueImage">Draw this when our data value is true</param>
/// <param name="falseImage">Draw this when our data value is false</param>
/// <returns>A Renderer</returns>
public static MappedImageRenderer Boolean(Object trueImage, Object falseImage) {
return new MappedImageRenderer(true, trueImage, false, falseImage);
}
/// <summary>
/// Return a renderer that draw tristate boolean values using the given images
/// </summary>
/// <param name="trueImage">Draw this when our data value is true</param>
/// <param name="falseImage">Draw this when our data value is false</param>
/// <param name="nullImage">Draw this when our data value is null</param>
/// <returns>A Renderer</returns>
public static MappedImageRenderer TriState(Object trueImage, Object falseImage, Object nullImage) {
return new MappedImageRenderer(new Object[] {true, trueImage, false, falseImage, null, nullImage});
}
/// <summary>
/// Make a new empty renderer
/// </summary>
public MappedImageRenderer() {
map = new System.Collections.Hashtable();
}
/// <summary>
/// Make a new renderer that will show the given image when the given key is the aspect value
/// </summary>
/// <param name="key">The data value to be matched</param>
/// <param name="image">The image to be shown when the key is matched</param>
public MappedImageRenderer(Object key, Object image)
: this() {
this.Add(key, image);
}
/// <summary>
/// Make a new renderer that will show the given images when it receives the given keys
/// </summary>
/// <param name="key1"></param>
/// <param name="image1"></param>
/// <param name="key2"></param>
/// <param name="image2"></param>
public MappedImageRenderer(Object key1, Object image1, Object key2, Object image2)
: this() {
this.Add(key1, image1);
this.Add(key2, image2);
}
/// <summary>
/// Build a renderer from the given array of keys and their matching images
/// </summary>
/// <param name="keysAndImages">An array of key/image pairs</param>
public MappedImageRenderer(Object[] keysAndImages)
: this() {
if ((keysAndImages.GetLength(0) % 2) != 0)
throw new ArgumentException("Array must have key/image pairs");
for (int i = 0; i < keysAndImages.GetLength(0); i += 2)
this.Add(keysAndImages[i], keysAndImages[i + 1]);
}
/// <summary>
/// Register the image that should be drawn when our Aspect has the data value.
/// </summary>
/// <param name="value">Value that the Aspect must match</param>
/// <param name="image">An ImageSelector -- an int, string or image</param>
public void Add(Object value, Object image) {
if (value == null)
this.nullImage = image;
else
map[value] = image;
}
/// <summary>
/// Render our value
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
public override void Render(Graphics g, Rectangle r) {
this.DrawBackground(g, r);
r = this.ApplyCellPadding(r);
ICollection aspectAsCollection = this.Aspect as ICollection;
if (aspectAsCollection == null)
this.RenderOne(g, r, this.Aspect);
else
this.RenderCollection(g, r, aspectAsCollection);
}
/// <summary>
/// Draw a collection of images
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="imageSelectors"></param>
protected void RenderCollection(Graphics g, Rectangle r, ICollection imageSelectors) {
ArrayList images = new ArrayList();
Image image = null;
foreach (Object selector in imageSelectors) {
if (selector == null)
image = this.GetImage(this.nullImage);
else if (map.ContainsKey(selector))
image = this.GetImage(map[selector]);
else
image = null;
if (image != null)
images.Add(image);
}
this.DrawImages(g, r, images);
}
/// <summary>
/// Draw one image
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="selector"></param>
protected void RenderOne(Graphics g, Rectangle r, Object selector) {
Image image = null;
if (selector == null)
image = this.GetImage(this.nullImage);
else if (map.ContainsKey(selector))
image = this.GetImage(map[selector]);
if (image != null)
this.DrawAlignedImage(g, r, image);
}
#region Private variables
private Hashtable map; // Track the association between values and images
private Object nullImage; // image to be drawn for null values (since null can't be a key)
#endregion
}
/// <summary>
/// This renderer draws just a checkbox to match the check state of our model object.
/// </summary>
public class CheckStateRenderer : BaseRenderer {
/// <summary>
/// Draw our cell
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
public override void Render(Graphics g, Rectangle r) {
this.DrawBackground(g, r);
if (this.Column == null)
return;
r = this.ApplyCellPadding(r);
CheckState state = this.Column.GetCheckState(this.RowObject);
if (this.IsPrinting) {
// Renderers don't work onto printer DCs, so we have to draw the image ourselves
string key = ObjectListView.CHECKED_KEY;
if (state == CheckState.Unchecked)
key = ObjectListView.UNCHECKED_KEY;
if (state == CheckState.Indeterminate)
key = ObjectListView.INDETERMINATE_KEY;
this.DrawAlignedImage(g, r, this.ImageListOrDefault.Images[key]);
} else {
r = this.CalculateCheckBoxBounds(g, r);
CheckBoxRenderer.DrawCheckBox(g, r.Location, this.GetCheckBoxState(state));
}
}
/// <summary>
/// Handle the GetEditRectangle request
/// </summary>
/// <param name="g"></param>
/// <param name="cellBounds"></param>
/// <param name="item"></param>
/// <param name="subItemIndex"></param>
/// <param name="preferredSize"> </param>
/// <returns></returns>
protected override Rectangle HandleGetEditRectangle(Graphics g, Rectangle cellBounds, OLVListItem item, int subItemIndex, Size preferredSize) {
return this.CalculatePaddedAlignedBounds(g, cellBounds, preferredSize);
}
/// <summary>
/// Handle the HitTest request
/// </summary>
/// <param name="g"></param>
/// <param name="hti"></param>
/// <param name="x"></param>
/// <param name="y"></param>
protected override void HandleHitTest(Graphics g, OlvListViewHitTestInfo hti, int x, int y) {
Rectangle r = this.CalculateCheckBoxBounds(g, this.Bounds);
if (r.Contains(x, y))
hti.HitTestLocation = HitTestLocation.CheckBox;
}
}
/// <summary>
/// Render an image that comes from our data source.
/// </summary>
/// <remarks>The image can be sourced from:
/// <list type="bullet">
/// <item><description>a byte-array (normally when the image to be shown is
/// stored as a value in a database)</description></item>
/// <item><description>an int, which is treated as an index into the image list</description></item>
/// <item><description>a string, which is treated first as a file name, and failing that as an index into the image list</description></item>
/// <item><description>an ICollection of ints or strings, which will be drawn as consecutive images</description></item>
/// </list>
/// <para>If an image is an animated GIF, it's state is stored in the SubItem object.</para>
/// <para>By default, the image renderer does not render animations (it begins life with animations paused).
/// To enable animations, you must call Unpause().</para>
/// <para>In the current implementation (2009-09), each column showing animated gifs must have a
/// different instance of ImageRenderer assigned to it. You cannot share the same instance of
/// an image renderer between two animated gif columns. If you do, only the last column will be
/// animated.</para>
/// </remarks>
public class ImageRenderer : BaseRenderer {
/// <summary>
/// Make an empty image renderer
/// </summary>
public ImageRenderer() {
this.stopwatch = new Stopwatch();
}
/// <summary>
/// Make an empty image renderer that begins life ready for animations
/// </summary>
public ImageRenderer(bool startAnimations)
: this() {
this.Paused = !startAnimations;
}
/// <summary>
/// Finalizer
/// </summary>
protected override void Dispose(bool disposing) {
Paused = true;
base.Dispose(disposing);
}
#region Properties
/// <summary>
/// Should the animations in this renderer be paused?
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool Paused {
get { return isPaused; }
set {
if (this.isPaused == value)
return;
this.isPaused = value;
if (this.isPaused) {
this.StopTickler();
this.stopwatch.Stop();
} else {
this.Tickler.Change(1, Timeout.Infinite);
this.stopwatch.Start();
}
}
}
private bool isPaused = true;
private void StopTickler() {
if (this.tickler == null)
return;
this.tickler.Dispose();
this.tickler = null;
}
/// <summary>
/// Gets a timer that can be used to trigger redraws on animations
/// </summary>
protected Timer Tickler {
get {
if (this.tickler == null)
this.tickler = new System.Threading.Timer(new TimerCallback(this.OnTimer), null, Timeout.Infinite, Timeout.Infinite);
return this.tickler;
}
}
#endregion
#region Commands
/// <summary>
/// Pause any animations
/// </summary>
public void Pause() {
this.Paused = true;
}
/// <summary>
/// Unpause any animations
/// </summary>
public void Unpause() {
this.Paused = false;
}
#endregion
#region Drawing
/// <summary>
/// Draw our image
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
public override void Render(Graphics g, Rectangle r) {
this.DrawBackground(g, r);
if (this.Aspect == null || this.Aspect == System.DBNull.Value)
return;
r = this.ApplyCellPadding(r);
if (this.Aspect is System.Byte[]) {
this.DrawAlignedImage(g, r, this.GetImageFromAspect());
} else {
ICollection imageSelectors = this.Aspect as ICollection;
if (imageSelectors == null)
this.DrawAlignedImage(g, r, this.GetImageFromAspect());
else
this.DrawImages(g, r, imageSelectors);
}
}
/// <summary>
/// Translate our Aspect into an image.
/// </summary>
/// <remarks>The strategy is:<list type="bullet">
/// <item><description>If its a byte array, we treat it as an in-memory image</description></item>
/// <item><description>If it's an int, we use that as an index into our image list</description></item>
/// <item><description>If it's a string, we try to load a file by that name. If we can't,
/// we use the string as an index into our image list.</description></item>
///</list></remarks>
/// <returns>An image</returns>
protected Image GetImageFromAspect() {
// If we've already figured out the image, don't do it again
if (this.OLVSubItem != null && this.OLVSubItem.ImageSelector is Image) {
if (this.OLVSubItem.AnimationState == null)
return (Image) this.OLVSubItem.ImageSelector;
else
return this.OLVSubItem.AnimationState.image;
}
// Try to convert our Aspect into an Image
// If its a byte array, we treat it as an in-memory image
// If it's an int, we use that as an index into our image list
// If it's a string, we try to find a file by that name.
// If we can't, we use the string as an index into our image list.
Image image = this.Aspect as Image;
if (image != null) {
// Don't do anything else
} else if (this.Aspect is System.Byte[]) {
using (MemoryStream stream = new MemoryStream((System.Byte[]) this.Aspect)) {
try {
image = Image.FromStream(stream);
}
catch (ArgumentException) {
// ignore
}
}
} else if (this.Aspect is Int32) {
image = this.GetImage(this.Aspect);
} else {
String str = this.Aspect as String;
if (!String.IsNullOrEmpty(str)) {
try {
image = Image.FromFile(str);
}
catch (FileNotFoundException) {
image = this.GetImage(this.Aspect);
}
catch (OutOfMemoryException) {
image = this.GetImage(this.Aspect);
}
}
}
// If this image is an animation, initialize the animation process
if (this.OLVSubItem != null && AnimationState.IsAnimation(image)) {
this.OLVSubItem.AnimationState = new AnimationState(image);
}
// Cache the image so we don't repeat this dreary process
if (this.OLVSubItem != null)
this.OLVSubItem.ImageSelector = image;
return image;
}
#endregion
#region Events
/// <summary>
/// This is the method that is invoked by the timer. It basically switches control to the listview thread.
/// </summary>
/// <param name="state">not used</param>
public void OnTimer(Object state) {
if (this.IsListViewDead)
return;
if (this.Paused)
return;
if (this.ListView.InvokeRequired)
this.ListView.Invoke((MethodInvoker) delegate { this.OnTimer(state); });
else
this.OnTimerInThread();
}
private bool IsListViewDead {
get {
// Apply a whole heap of sanity checks, which basically ensure that the ListView is still alive
return this.ListView == null ||
this.ListView.Disposing ||
this.ListView.IsDisposed ||
!this.ListView.IsHandleCreated;
}
}
/// <summary>
/// This is the OnTimer callback, but invoked in the same thread as the creator of the ListView.
/// This method can use all of ListViews methods without creating a CrossThread exception.
/// </summary>
protected void OnTimerInThread() {
// MAINTAINER NOTE: This method must renew the tickler. If it doesn't the animations will stop.
// If this listview has been destroyed, we can't do anything, so we return without
// renewing the tickler, effectively killing all animations on this renderer
if (this.IsListViewDead)
return;
if (this.Paused)
return;
// If we're not in Detail view or our column has been removed from the list,
// we can't do anything at the moment, but we still renew the tickler because the view may change later.
if (this.ListView.View != System.Windows.Forms.View.Details || this.Column == null || this.Column.Index < 0) {
this.Tickler.Change(1000, Timeout.Infinite);
return;
}
long elapsedMilliseconds = this.stopwatch.ElapsedMilliseconds;
int subItemIndex = this.Column.Index;
long nextCheckAt = elapsedMilliseconds + 1000; // wait at most one second before checking again
Rectangle updateRect = new Rectangle(); // what part of the view must be updated to draw the changed gifs?
// Run through all the subitems in the view for our column, and for each one that
// has an animation attached to it, see if the frame needs updating.
for (int i = 0; i < this.ListView.GetItemCount(); i++) {
OLVListItem lvi = this.ListView.GetItem(i);
// Get the animation state from the subitem. If there isn't an animation state, skip this row.
OLVListSubItem lvsi = lvi.GetSubItem(subItemIndex);
AnimationState state = lvsi.AnimationState;
if (state == null || !state.IsValid)
continue;
// Has this frame of the animation expired?
if (elapsedMilliseconds >= state.currentFrameExpiresAt) {
state.AdvanceFrame(elapsedMilliseconds);
// Track the area of the view that needs to be redrawn to show the changed images
if (updateRect.IsEmpty)
updateRect = lvsi.Bounds;
else
updateRect = Rectangle.Union(updateRect, lvsi.Bounds);
}
// Remember the minimum time at which a frame is next due to change
nextCheckAt = Math.Min(nextCheckAt, state.currentFrameExpiresAt);
}
// Update the part of the listview where frames have changed
if (!updateRect.IsEmpty)
this.ListView.Invalidate(updateRect);
// Renew the tickler in time for the next frame change
this.Tickler.Change(nextCheckAt - elapsedMilliseconds, Timeout.Infinite);
}
#endregion
/// <summary>
/// Instances of this class kept track of the animation state of a single image.
/// </summary>
internal class AnimationState {
private const int PropertyTagTypeShort = 3;
private const int PropertyTagTypeLong = 4;
private const int PropertyTagFrameDelay = 0x5100;
private const int PropertyTagLoopCount = 0x5101;
/// <summary>
/// Is the given image an animation
/// </summary>
/// <param name="image">The image to be tested</param>
/// <returns>Is the image an animation?</returns>
public static bool IsAnimation(Image image) {
if (image == null)
return false;
else
return (new List<Guid>(image.FrameDimensionsList)).Contains(FrameDimension.Time.Guid);
}
/// <summary>
/// Create an AnimationState in a quiet state
/// </summary>
public AnimationState() {
this.imageDuration = new List<int>();
}
/// <summary>
/// Create an animation state for the given image, which may or may not
/// be an animation
/// </summary>
/// <param name="image">The image to be rendered</param>
public AnimationState(Image image)
: this() {
if (!AnimationState.IsAnimation(image))
return;
// How many frames in the animation?
this.image = image;
this.frameCount = this.image.GetFrameCount(FrameDimension.Time);
// Find the delay between each frame.
// The delays are stored an array of 4-byte ints. Each int is the
// number of 1/100th of a second that should elapsed before the frame expires
foreach (PropertyItem pi in this.image.PropertyItems) {
if (pi.Id == PropertyTagFrameDelay) {
for (int i = 0; i < pi.Len; i += 4) {
//TODO: There must be a better way to convert 4-bytes to an int
int delay = (pi.Value[i + 3] << 24) + (pi.Value[i + 2] << 16) + (pi.Value[i + 1] << 8) + pi.Value[i];
this.imageDuration.Add(delay * 10); // store delays as milliseconds
}
break;
}
}
// There should be as many frame durations as frames
Debug.Assert(this.imageDuration.Count == this.frameCount, "There should be as many frame durations as there are frames.");
}
/// <summary>
/// Does this state represent a valid animation
/// </summary>
public bool IsValid {
get { return (this.image != null && this.frameCount > 0); }
}
/// <summary>
/// Advance our images current frame and calculate when it will expire
/// </summary>
public void AdvanceFrame(long millisecondsNow) {
this.currentFrame = (this.currentFrame + 1) % this.frameCount;
this.currentFrameExpiresAt = millisecondsNow + this.imageDuration[this.currentFrame];
this.image.SelectActiveFrame(FrameDimension.Time, this.currentFrame);
}
internal int currentFrame;
internal long currentFrameExpiresAt;
internal Image image;
internal List<int> imageDuration;
internal int frameCount;
}
#region Private variables
private System.Threading.Timer tickler; // timer used to tickle the animations
private Stopwatch stopwatch; // clock used to time the animation frame changes
#endregion
}
/// <summary>
/// Render our Aspect as a progress bar
/// </summary>
public class BarRenderer : BaseRenderer {
#region Constructors
/// <summary>
/// Make a BarRenderer
/// </summary>
public BarRenderer()
: base() {}
/// <summary>
/// Make a BarRenderer for the given range of data values
/// </summary>
public BarRenderer(int minimum, int maximum)
: this() {
this.MinimumValue = minimum;
this.MaximumValue = maximum;
}
/// <summary>
/// Make a BarRenderer using a custom bar scheme
/// </summary>
public BarRenderer(Pen pen, Brush brush)
: this() {
this.Pen = pen;
this.Brush = brush;
this.UseStandardBar = false;
}
/// <summary>
/// Make a BarRenderer using a custom bar scheme
/// </summary>
public BarRenderer(int minimum, int maximum, Pen pen, Brush brush)
: this(minimum, maximum) {
this.Pen = pen;
this.Brush = brush;
this.UseStandardBar = false;
}
/// <summary>
/// Make a BarRenderer that uses a horizontal gradient
/// </summary>
public BarRenderer(Pen pen, Color start, Color end)
: this() {
this.Pen = pen;
this.SetGradient(start, end);
}
/// <summary>
/// Make a BarRenderer that uses a horizontal gradient
/// </summary>
public BarRenderer(int minimum, int maximum, Pen pen, Color start, Color end)
: this(minimum, maximum) {
this.Pen = pen;
this.SetGradient(start, end);
}
#endregion
#region Configuration Properties
/// <summary>
/// 此栏是否应以系统样式绘制?
/// </summary>
[Category("ObjectListView"),
Description("此栏是否应以系统样式绘制"),
DefaultValue(true)]
public bool UseStandardBar {
get { return useStandardBar; }
set { useStandardBar = value; }
}
private bool useStandardBar = true;
/// <summary>
/// 将从我们的单元格边框开始绘制多少像素的条形图
/// </summary>
[Category("ObjectListView"),
Description("将从我们的单元格边框开始绘制多少像素的条形图"),
DefaultValue(2)]
public int Padding {
get { return padding; }
set { padding = value; }
}
private int padding = 2;
/// <summary>
///在绘制进度条之前,将使用什么颜色填充控件内部?
/// </summary>
[Category("ObjectListView"),
Description("条形图内部的颜色"),
DefaultValue(typeof (Color), "AliceBlue")]
public Color BackgroundColor {
get { return backgroundColor; }
set { backgroundColor = value; }
}
private Color backgroundColor = Color.AliceBlue;
/// <summary>
/// 进度条边框颜色
/// </summary>
[Category("ObjectListView"),
Description("进度条边框颜色"),
DefaultValue(typeof (Color), "Black")]
public Color FrameColor {
get { return frameColor; }
set { frameColor = value; }
}
private Color frameColor = Color.Black;
/// <summary>
/// 进度条的边框像素宽
/// </summary>
[Category("ObjectListView"),
Description("进度条的边框像素宽"),
DefaultValue(1.0f)]
public float FrameWidth {
get { return frameWidth; }
set { frameWidth = value; }
}
private float frameWidth = 1.0f;
/// <summary>
/// 进度条的“填充”部分应该是什么颜色?
/// </summary>
/// <remarks>仅当GradientStartColor为Color.Empty时才使用此选项</remarks>
[Category("ObjectListView"),
Description("进度条的“填充”部分应该是什么颜色?"),
DefaultValue(typeof (Color), "BlueViolet")]
public Color FillColor {
get { return fillColor; }
set { fillColor = value; }
}
private Color fillColor = Color.BlueViolet;
/// <summary>
/// 进度条中是否显示文本百分比进度显示
/// </summary>
/// <remarks></remarks>
[Category("ObjectListView"),
Description("进度条中是否显示文本百分比进度显示"),
DefaultValue(typeof(Boolean), "True")]
public bool ShowTextPercent
{
get { return showTextPercent; }
set { showTextPercent = value; }
}
private bool showTextPercent = true;
/// <summary>
/// 使用渐变以此颜色开始填充进度条
/// </summary>
[Category("ObjectListView"),
Description("使用渐变以此颜色开始填充进度条"),
DefaultValue(typeof (Color), "CornflowerBlue")]
public Color GradientStartColor {
get { return startColor; }
set { startColor = value; }
}
private Color startColor = Color.CornflowerBlue;
/// <summary>
/// 使用渐变填充以此颜色结尾的进度条
/// </summary>
[Category("ObjectListView"),
Description("使用渐变填充以此颜色结尾的进度条"),
DefaultValue(typeof (Color), "DarkBlue")]
public Color GradientEndColor {
get { return endColor; }
set { endColor = value; }
}
private Color endColor = Color.DarkBlue;
/// <summary>
/// 使用渐变以此颜色开始填充渐满进度条
/// </summary>
[Category("ObjectListView"),
Description("使用渐变以此颜色开始填充渐满进度条"),
DefaultValue(typeof(Color), "CornflowerBlue")]
public Color NearGradientStartColor
{
get { return near_startColor; }
set { near_startColor = value; }
}
private Color near_startColor = Color.CornflowerBlue;
/// <summary>
/// 使用渐变填充以此颜色结尾的渐满进度条
/// </summary>
[Category("ObjectListView"),
Description("使用渐变填充以此颜色结尾的渐满进度条"),
DefaultValue(typeof(Color), "DarkBlue")]
public Color NearGradientEndColor
{
get { return near_endColor; }
set { near_endColor = value; }
}
private Color near_endColor = Color.DarkBlue;
/// <summary>
/// 无论列变得多宽,进度条都不会比这个宽。
/// </summary>
[Category("Behavior"),
Description("进度条永远不会比这个更宽"),
DefaultValue(100)]
public int MaximumWidth {
get { return maximumWidth; }
set { maximumWidth = value; }
}
private int maximumWidth = 100;
/// <summary>
/// 无论单元格有多高,进度条都不会比这个高
/// </summary>
[Category("Behavior"),
Description("进度条永远不会比这个高"),
DefaultValue(16)]
public int MaximumHeight {
get { return maximumHeight; }
set { maximumHeight = value; }
}
private int maximumHeight = 16;
/// <summary>
/// 预期的最小数据值。小于此值的值将显示一个空条。
/// </summary>
[Category("Behavior"),
Description("预期的最小数据值。小于此值的值将显示一个空条。"),
DefaultValue(0.0)]
public double MinimumValue {
get { return minimumValue; }
set { minimumValue = value; }
}
private double minimumValue = 0.0;
/// <summary>
/// 接近满时的值,高于该值时显示不同颜色小于等于MinimumValue或大于MaximumValue值则表示未设置该值
/// </summary>
[Category("Behavior"),
Description("接近满时的值,高于该值时显示不同颜色小于等于MinimumValue或大于MaximumValue值则表示未设置该值"),
DefaultValue(0.0)]
public double NearFullValue
{
get { return nearfullValue; }
set { nearfullValue = value; }
}
private double nearfullValue = 0.0;
/// <summary>
///范围的最大值。大于此值的值将给出一个完整的条形。
/// </summary>
[Category("Behavior"),
Description("范围的最大值。大于此值的值将给出一个完整的条形。"),
DefaultValue(100.0)]
public double MaximumValue {
get { return maximumValue; }
set { maximumValue = value; }
}
private double maximumValue = 100.0;
#endregion
#region Public Properties (non-IDE)
/// <summary>
/// The Pen that will draw the frame surrounding this bar
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Pen Pen {
get {
if (this.pen == null && !this.FrameColor.IsEmpty)
return new Pen(this.FrameColor, this.FrameWidth);
else
return this.pen;
}
set { this.pen = value; }
}
private Pen pen;
/// <summary>
/// The brush that will be used to fill the bar
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Brush Brush {
get {
if (this.brush == null && !this.FillColor.IsEmpty)
return new SolidBrush(this.FillColor);
else
return this.brush;
}
set { this.brush = value; }
}
private Brush brush;
/// <summary>
/// The brush that will be used to fill the bar
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Brush NearBrush
{
get
{
if (this.near_brush == null && !this.FillColor.IsEmpty)
return new SolidBrush(this.FillColor);
else
return this.near_brush;
}
set { this.near_brush = value; }
}
private Brush near_brush;
/// <summary>
/// The brush that will be used to fill the background of the bar
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Brush BackgroundBrush {
get {
if (this.backgroundBrush == null && !this.BackgroundColor.IsEmpty)
return new SolidBrush(this.BackgroundColor);
else
return this.backgroundBrush;
}
set { this.backgroundBrush = value; }
}
private Brush backgroundBrush;
#endregion
/// <summary>
/// Draw this progress bar using a gradient
/// </summary>
/// <param name="start"></param>
/// <param name="end"></param>
public void SetGradient(Color start, Color end) {
this.GradientStartColor = start;
this.GradientEndColor = end;
}
/// <summary>
/// Draw our aspect
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
public override void Render(Graphics g, Rectangle r) {
this.DrawBackground(g, r);
r = this.ApplyCellPadding(r);
Rectangle frameRect = Rectangle.Inflate(r, 0 - this.Padding, 0 - this.Padding);
frameRect.Width = Math.Min(frameRect.Width, this.MaximumWidth);
frameRect.Height = Math.Min(frameRect.Height, this.MaximumHeight);
frameRect = this.AlignRectangle(r, frameRect);
// Convert our aspect to a numeric value
IConvertible convertable = this.Aspect as IConvertible;
if (convertable == null)
return;
double aspectValue = convertable.ToDouble(NumberFormatInfo.InvariantInfo);
Rectangle fillRect = Rectangle.Inflate(frameRect, -1, -1);
if (aspectValue <= this.MinimumValue)
fillRect.Width = 0;
else if (aspectValue < this.MaximumValue)
fillRect.Width = (int) (fillRect.Width * (aspectValue - this.MinimumValue) / this.MaximumValue);
// MS-themed progress bars don't work when printing
if (this.UseStandardBar && ProgressBarRenderer.IsSupported && !this.IsPrinting) {
ProgressBarRenderer.DrawHorizontalBar(g, frameRect);
ProgressBarRenderer.DrawHorizontalChunks(g, fillRect);
} else {
g.FillRectangle(this.BackgroundBrush, frameRect);
if (fillRect.Width > 0) {
// FillRectangle fills inside the given rectangle, so expand it a little
fillRect.Width++;
fillRect.Height++;
if (aspectValue >= nearfullValue && nearfullValue >= this.MinimumValue && nearfullValue<= this.MaximumValue)
{
if (this.NearGradientStartColor == Color.Empty)
g.FillRectangle(this.NearBrush, fillRect);
else
{
using (LinearGradientBrush gradient = new LinearGradientBrush(frameRect, this.NearGradientStartColor, this.NearGradientEndColor, LinearGradientMode.Horizontal))
{
g.FillRectangle(gradient, fillRect);
}
}
}
else
{
if (this.GradientStartColor == Color.Empty)
g.FillRectangle(this.Brush, fillRect);
else
{
using (LinearGradientBrush gradient = new LinearGradientBrush(frameRect, this.GradientStartColor, this.GradientEndColor, LinearGradientMode.Horizontal))
{
g.FillRectangle(gradient, fillRect);
}
}
}
}
g.DrawRectangle(this.Pen, frameRect);
}
if (ShowTextPercent)
{
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
g.DrawString(aspectValue.ToNString("0.00") + "%", this.Font, this.TextBrush, frameRect, format);
}
}
/// <summary>
/// Handle the GetEditRectangle request
/// </summary>
/// <param name="g"></param>
/// <param name="cellBounds"></param>
/// <param name="item"></param>
/// <param name="subItemIndex"></param>
/// <param name="preferredSize"> </param>
/// <returns></returns>
protected override Rectangle HandleGetEditRectangle(Graphics g, Rectangle cellBounds, OLVListItem item, int subItemIndex, Size preferredSize) {
return this.CalculatePaddedAlignedBounds(g, cellBounds, preferredSize);
}
}
/// <summary>
/// An ImagesRenderer draws zero or more images depending on the data returned by its Aspect.
/// </summary>
/// <remarks><para>This renderer's Aspect must return a ICollection of ints, strings or Images,
/// each of which will be drawn horizontally one after the other.</para>
/// <para>As of v2.1, this functionality has been absorbed into ImageRenderer and this is now an
/// empty shell, solely for backwards compatibility.</para>
/// </remarks>
[ToolboxItem(false)]
public class ImagesRenderer : ImageRenderer {}
/// <summary>
/// A MultiImageRenderer draws the same image a number of times based on our data value
/// </summary>
/// <remarks><para>The stars in the Rating column of iTunes is a good example of this type of renderer.</para></remarks>
public class MultiImageRenderer : BaseRenderer {
/// <summary>
/// Make a quiet renderer
/// </summary>
public MultiImageRenderer()
: base() {}
/// <summary>
/// Make an image renderer that will draw the indicated image, at most maxImages times.
/// </summary>
/// <param name="imageSelector"></param>
/// <param name="maxImages"></param>
/// <param name="minValue"></param>
/// <param name="maxValue"></param>
public MultiImageRenderer(Object imageSelector, int maxImages, int minValue, int maxValue)
: this() {
this.ImageSelector = imageSelector;
this.MaxNumberImages = maxImages;
this.MinimumValue = minValue;
this.MaximumValue = maxValue;
}
#region Configuration Properties
/// <summary>
/// The index of the image that should be drawn
/// </summary>
[Category("Behavior"),
Description("The index of the image that should be drawn"),
DefaultValue(-1)]
public int ImageIndex {
get {
if (imageSelector is Int32)
return (Int32) imageSelector;
else
return -1;
}
set { imageSelector = value; }
}
/// <summary>
/// The name of the image that should be drawn
/// </summary>
[Category("Behavior"),
Description("The index of the image that should be drawn"),
DefaultValue(null)]
public string ImageName {
get { return imageSelector as String; }
set { imageSelector = value; }
}
/// <summary>
/// The image selector that will give the image to be drawn
/// </summary>
/// <remarks>Like all image selectors, this can be an int, string or Image</remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Object ImageSelector {
get { return imageSelector; }
set { imageSelector = value; }
}
private Object imageSelector;
/// <summary>
/// What is the maximum number of images that this renderer should draw?
/// </summary>
[Category("Behavior"),
Description("The maximum number of images that this renderer should draw"),
DefaultValue(10)]
public int MaxNumberImages {
get { return maxNumberImages; }
set { maxNumberImages = value; }
}
private int maxNumberImages = 10;
/// <summary>
/// Values less than or equal to this will have 0 images drawn
/// </summary>
[Category("Behavior"),
Description("Values less than or equal to this will have 0 images drawn"),
DefaultValue(0)]
public int MinimumValue {
get { return minimumValue; }
set { minimumValue = value; }
}
private int minimumValue = 0;
/// <summary>
/// Values greater than or equal to this will have MaxNumberImages images drawn
/// </summary>
[Category("Behavior"),
Description("Values greater than or equal to this will have MaxNumberImages images drawn"),
DefaultValue(100)]
public int MaximumValue {
get { return maximumValue; }
set { maximumValue = value; }
}
private int maximumValue = 100;
#endregion
/// <summary>
/// Draw our data value
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
public override void Render(Graphics g, Rectangle r) {
this.DrawBackground(g, r);
r = this.ApplyCellPadding(r);
Image image = this.GetImage(this.ImageSelector);
if (image == null)
return;
// Convert our aspect to a numeric value
IConvertible convertable = this.Aspect as IConvertible;
if (convertable == null)
return;
double aspectValue = convertable.ToDouble(NumberFormatInfo.InvariantInfo);
// Calculate how many images we need to draw to represent our aspect value
int numberOfImages;
if (aspectValue <= this.MinimumValue)
numberOfImages = 0;
else if (aspectValue < this.MaximumValue)
numberOfImages = 1 + (int) (this.MaxNumberImages * (aspectValue - this.MinimumValue) / this.MaximumValue);
else
numberOfImages = this.MaxNumberImages;
// If we need to shrink the image, what will its on-screen dimensions be?
int imageScaledWidth = image.Width;
int imageScaledHeight = image.Height;
if (r.Height < image.Height) {
imageScaledWidth = (int) ((float) image.Width * (float) r.Height / (float) image.Height);
imageScaledHeight = r.Height;
}
// Calculate where the images should be drawn
Rectangle imageBounds = r;
imageBounds.Width = (this.MaxNumberImages * (imageScaledWidth + this.Spacing)) - this.Spacing;
imageBounds.Height = imageScaledHeight;
imageBounds = this.AlignRectangle(r, imageBounds);
// Finally, draw the images
Rectangle singleImageRect = new Rectangle(imageBounds.X, imageBounds.Y, imageScaledWidth, imageScaledHeight);
Color backgroundColor = GetBackgroundColor();
for (int i = 0; i < numberOfImages; i++) {
if (this.ListItem.Enabled) {
this.DrawImage(g, singleImageRect, this.ImageSelector);
} else
ControlPaint.DrawImageDisabled(g, image, singleImageRect.X, singleImageRect.Y, backgroundColor);
singleImageRect.X += (imageScaledWidth + this.Spacing);
}
}
}
/// <summary>
/// A class to render a value that contains a bitwise-OR'ed collection of values.
/// </summary>
public class FlagRenderer : BaseRenderer {
/// <summary>
/// Register the given image to the given value
/// </summary>
/// <param name="key">When this flag is present...</param>
/// <param name="imageSelector">...draw this image</param>
public void Add(Object key, Object imageSelector) {
Int32 k2 = ((IConvertible) key).ToInt32(NumberFormatInfo.InvariantInfo);
this.imageMap[k2] = imageSelector;
this.keysInOrder.Remove(k2);
this.keysInOrder.Add(k2);
}
/// <summary>
/// Draw the flags
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
public override void Render(Graphics g, Rectangle r) {
this.DrawBackground(g, r);
IConvertible convertable = this.Aspect as IConvertible;
if (convertable == null)
return;
r = this.ApplyCellPadding(r);
Int32 v2 = convertable.ToInt32(NumberFormatInfo.InvariantInfo);
ArrayList images = new ArrayList();
foreach (Int32 key in this.keysInOrder) {
if ((v2 & key) == key) {
Image image = this.GetImage(this.imageMap[key]);
if (image != null)
images.Add(image);
}
}
if (images.Count > 0)
this.DrawImages(g, r, images);
}
/// <summary>
/// Do the actual work of hit testing. Subclasses should override this rather than HitTest()
/// </summary>
/// <param name="g"></param>
/// <param name="hti"></param>
/// <param name="x"></param>
/// <param name="y"></param>
protected override void HandleHitTest(Graphics g, OlvListViewHitTestInfo hti, int x, int y) {
IConvertible convertable = this.Aspect as IConvertible;
if (convertable == null)
return;
Int32 v2 = convertable.ToInt32(NumberFormatInfo.InvariantInfo);
Point pt = this.Bounds.Location;
foreach (Int32 key in this.keysInOrder) {
if ((v2 & key) == key) {
Image image = this.GetImage(this.imageMap[key]);
if (image != null) {
Rectangle imageRect = new Rectangle(pt, image.Size);
if (imageRect.Contains(x, y)) {
hti.UserData = key;
return;
}
pt.X += (image.Width + this.Spacing);
}
}
}
}
private List<Int32> keysInOrder = new List<Int32>();
private Dictionary<Int32, Object> imageMap = new Dictionary<Int32, object>();
}
/// <summary>
/// This renderer draws an image, a single line title, and then multi-line description
/// under the title.
/// </summary>
/// <remarks>
/// <para>This class works best with FullRowSelect = true.</para>
/// <para>It's not designed to work with cell editing -- it will work but will look odd.</para>
/// <para>
/// It's not RightToLeft friendly.
/// </para>
/// </remarks>
public class DescribedTaskRenderer : BaseRenderer, IFilterAwareRenderer
{
private readonly StringFormat noWrapStringFormat;
private readonly HighlightTextRenderer highlightTextRenderer = new HighlightTextRenderer();
/// <summary>
/// Create a DescribedTaskRenderer
/// </summary>
public DescribedTaskRenderer() {
this.noWrapStringFormat = new StringFormat(StringFormatFlags.NoWrap);
this.noWrapStringFormat.Trimming = StringTrimming.EllipsisCharacter;
this.noWrapStringFormat.Alignment = StringAlignment.Near;
this.noWrapStringFormat.LineAlignment = StringAlignment.Near;
this.highlightTextRenderer.CellVerticalAlignment = StringAlignment.Near;
}
#region Configuration properties
/// <summary>
/// Should text be rendered using GDI routines? This makes the text look more
/// like a native List view control.
/// </summary>
public override bool UseGdiTextRendering
{
get { return base.UseGdiTextRendering; }
set
{
base.UseGdiTextRendering = value;
this.highlightTextRenderer.UseGdiTextRendering = value;
}
}
/// <summary>
/// Gets or set the font that will be used to draw the title of the task
/// </summary>
/// <remarks>If this is null, the ListView's font will be used</remarks>
[Category("ObjectListView"),
Description("The font that will be used to draw the title of the task"),
DefaultValue(null)]
public Font TitleFont {
get { return titleFont; }
set { titleFont = value; }
}
private Font titleFont;
/// <summary>
/// Return a font that has been set for the title or a reasonable default
/// </summary>
[Browsable(false)]
public Font TitleFontOrDefault {
get { return this.TitleFont ?? this.ListView.Font; }
}
/// <summary>
/// Gets or set the color of the title of the task
/// </summary>
/// <remarks>This color is used when the task is not selected or when the listview
/// has a translucent selection mechanism.</remarks>
[Category("ObjectListView"),
Description("The color of the title"),
DefaultValue(typeof (Color), "")]
public Color TitleColor {
get { return titleColor; }
set { titleColor = value; }
}
private Color titleColor;
/// <summary>
/// Return the color of the title of the task or a reasonable default
/// </summary>
[Browsable(false)]
public Color TitleColorOrDefault {
get {
if (!this.ListItem.Enabled)
return this.SubItem.ForeColor;
if (this.IsItemSelected || this.TitleColor.IsEmpty)
return this.GetForegroundColor();
return this.TitleColor;
}
}
/// <summary>
/// Gets or set the font that will be used to draw the description of the task
/// </summary>
/// <remarks>If this is null, the ListView's font will be used</remarks>
[Category("ObjectListView"),
Description("The font that will be used to draw the description of the task"),
DefaultValue(null)]
public Font DescriptionFont {
get { return descriptionFont; }
set { descriptionFont = value; }
}
private Font descriptionFont;
/// <summary>
/// Return a font that has been set for the title or a reasonable default
/// </summary>
[Browsable(false)]
public Font DescriptionFontOrDefault {
get { return this.DescriptionFont ?? this.ListView.Font; }
}
/// <summary>
/// Gets or set the color of the description of the task
/// </summary>
/// <remarks>This color is used when the task is not selected or when the listview
/// has a translucent selection mechanism.</remarks>
[Category("ObjectListView"),
Description("The color of the description"),
DefaultValue(typeof (Color), "")]
public Color DescriptionColor {
get { return descriptionColor; }
set { descriptionColor = value; }
}
private Color descriptionColor = Color.Empty;
/// <summary>
/// Return the color of the description of the task or a reasonable default
/// </summary>
[Browsable(false)]
public Color DescriptionColorOrDefault {
get {
if (!this.ListItem.Enabled)
return this.SubItem.ForeColor;
if (this.IsItemSelected && !this.ListView.UseTranslucentSelection)
return this.GetForegroundColor();
return this.DescriptionColor.IsEmpty ? defaultDescriptionColor : this.DescriptionColor;
}
}
private static Color defaultDescriptionColor = Color.FromArgb(45, 46, 49);
/// <summary>
/// Gets or sets the number of pixels that will be left between the image and the text
/// </summary>
[Category("ObjectListView"),
Description("The number of pixels that that will be left between the image and the text"),
DefaultValue(4)]
public int ImageTextSpace
{
get { return imageTextSpace; }
set { imageTextSpace = value; }
}
private int imageTextSpace = 4;
/// <summary>
/// 顶部留白像素
/// </summary>
[Category("ObjectListView"),
Description("顶部留白像素"),
DefaultValue(4)]
public int TopSpace
{
get { return topSpace; }
set { topSpace = value; }
}
private int topSpace = 4;
/// <summary>
/// Gets or sets the number of pixels that will be left between the title and the description
/// </summary>
[Category("ObjectListView"),
Description("The number of pixels that that will be left between the title and the description"),
DefaultValue(2)]
public int TitleDescriptionSpace
{
get { return titleDescriptionSpace; }
set { titleDescriptionSpace = value; }
}
private int titleDescriptionSpace = 2;
/// <summary>
/// Gets or sets the name of the aspect of the model object that contains the task description
/// </summary>
[Category("ObjectListView"),
Description("The name of the aspect of the model object that contains the task description"),
DefaultValue(null)]
public string DescriptionAspectName {
get { return descriptionAspectName; }
set { descriptionAspectName = value; }
}
private string descriptionAspectName;
#endregion
#region Text highlighting
/// <summary>
/// Gets or sets the filter that is filtering the ObjectListView and for
/// which this renderer should highlight text
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public TextMatchFilter Filter
{
get { return this.highlightTextRenderer.Filter; }
set { this.highlightTextRenderer.Filter = value; }
}
/// <summary>
/// When a filter changes, keep track of the text matching filters
/// </summary>
IModelFilter IFilterAwareRenderer.Filter {
get { return this.Filter; }
set { this.highlightTextRenderer.RegisterNewFilter(value); }
}
#endregion
#region Calculating
/// <summary>
/// Fetch the description from the model class
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public virtual string GetDescription(object model) {
if (String.IsNullOrEmpty(this.DescriptionAspectName))
return String.Empty;
if (this.descriptionGetter == null)
this.descriptionGetter = new Munger(this.DescriptionAspectName);
return this.descriptionGetter.GetValue(model) as string;
}
private Munger descriptionGetter;
#endregion
#region Rendering
/// <summary>
///
/// </summary>
/// <param name="e"></param>
/// <param name="cellBounds"></param>
/// <param name="model"></param>
public override void ConfigureSubItem(DrawListViewSubItemEventArgs e, Rectangle cellBounds, object model) {
base.ConfigureSubItem(e, cellBounds, model);
this.highlightTextRenderer.ConfigureSubItem(e, cellBounds, model);
}
/// <summary>
/// Draw our item
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
public override void Render(Graphics g, Rectangle r) {
this.DrawBackground(g, r);
r = this.ApplyCellPadding(r);
this.DrawDescribedTask(g, r, this.GetText(), this.GetDescription(this.RowObject), this.GetImageSelector());
}
/// <summary>
/// Draw the task
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="title"></param>
/// <param name="description"></param>
/// <param name="imageSelector"></param>
protected virtual void DrawDescribedTask(Graphics g, Rectangle r, string title, string description, object imageSelector) {
//Debug.WriteLine(String.Format("DrawDescribedTask({0}, {1}, {2}, {3})", r, title, description, imageSelector));
// Draw the image if one's been given
Rectangle textBounds = r;
if (imageSelector != null) {
int imageWidth = this.DrawImage(g, r, imageSelector);
int gapToText = imageWidth + this.ImageTextSpace;
textBounds.Y += TopSpace;
textBounds.X += gapToText;
textBounds.Width -= gapToText;
}
// Draw the title
if (!String.IsNullOrEmpty(title)) {
using (SolidBrush b = new SolidBrush(this.TitleColorOrDefault)) {
this.highlightTextRenderer.CanWrap = false;
this.highlightTextRenderer.Font = this.TitleFontOrDefault;
this.highlightTextRenderer.TextBrush = b;
this.highlightTextRenderer.DrawText(g, textBounds, title);
}
// How tall was the title?
SizeF size = g.MeasureString(title, this.TitleFontOrDefault, textBounds.Width, this.noWrapStringFormat);
int pixelsToDescription = this.TitleDescriptionSpace + (int)size.Height;
textBounds.Y += pixelsToDescription;
textBounds.Height -= pixelsToDescription;
}
// Draw the description
if (!String.IsNullOrEmpty(description)) {
using (SolidBrush b = new SolidBrush(this.DescriptionColorOrDefault)) {
this.highlightTextRenderer.CanWrap = true;
this.highlightTextRenderer.Font = this.DescriptionFontOrDefault;
this.highlightTextRenderer.TextBrush = b;
this.highlightTextRenderer.DrawText(g, textBounds, description);
}
}
//g.DrawRectangle(Pens.OrangeRed, r);
}
#endregion
#region Hit Testing
/// <summary>
/// Handle the HitTest request
/// </summary>
/// <param name="g"></param>
/// <param name="hti"></param>
/// <param name="x"></param>
/// <param name="y"></param>
protected override void HandleHitTest(Graphics g, OlvListViewHitTestInfo hti, int x, int y) {
if (this.Bounds.Contains(x, y))
hti.HitTestLocation = HitTestLocation.Text;
}
#endregion
}
/// <summary>
/// This renderer draws a functioning button in its cell
/// </summary>
public class ColumnButtonRenderer : BaseRenderer {
#region Properties
/// <summary>
/// Gets or sets how each button will be sized
/// </summary>
[Category("ObjectListView"),
Description("How each button will be sized"),
DefaultValue(OLVColumn.ButtonSizingMode.TextBounds)]
public OLVColumn.ButtonSizingMode SizingMode
{
get { return this.sizingMode; }
set { this.sizingMode = value; }
}
private OLVColumn.ButtonSizingMode sizingMode = OLVColumn.ButtonSizingMode.CellBounds;
/// <summary>
/// 获取或设置SizingMode为FixedBounds时按钮的大小
/// </summary>
/// <remarks>If this is not set, the bounds of the cell will be used</remarks>
[Category("ObjectListView"),
Description("SizingMode为FixedBounds时按钮的大小"),
DefaultValue(null)]
public Size? ButtonSize
{
get { return this.buttonSize; }
set { this.buttonSize = value; }
}
private Size? buttonSize;
/// <summary>
/// 获取或设置SizingMode为TextBounds时单元格周围的额外空间
/// </summary>
[Category("ObjectListView"),
Description("SizingMode为TextBounds时围绕单元格的额外空间")]
public Size? ButtonPadding
{
get { return this.buttonPadding; }
set { this.buttonPadding = value; }
}
private Size? buttonPadding = new Size(0, 0);
private Size ButtonPaddingOrDefault {
get { return this.ButtonPadding ?? new Size(0, 0); }
}
/// <summary>
/// Gets or sets the maximum width that a button can occupy.
/// -1 means there is no maximum width.
/// </summary>
/// <remarks>This is only considered when the SizingMode is TextBounds</remarks>
[Category("ObjectListView"),
Description("The maximum width that a button can occupy when the SizingMode is TextBounds"),
DefaultValue(-1)]
public int MaxButtonWidth
{
get { return this.maxButtonWidth; }
set { this.maxButtonWidth = value; }
}
private int maxButtonWidth = -1;
/// <summary>
/// Gets or sets the minimum width that a button can occupy.
/// -1 means there is no minimum width.
/// </summary>
/// <remarks>This is only considered when the SizingMode is TextBounds</remarks>
[Category("ObjectListView"),
Description("The minimum width that a button can be when the SizingMode is TextBounds"),
DefaultValue(-1)]
public int MinButtonWidth {
get { return this.minButtonWidth; }
set { this.minButtonWidth = value; }
}
private int minButtonWidth = -1;
/// <summary>
///获取或设置此列按钮的字体颜色(如果有按钮的话)
/// </summary>
[Category("ObjectListView"),
Description("获取或设置此列按钮的字体颜色(如果有按钮的话)")]
public Color ButtonForeColor
{
get;
set;
} = Color.White;
/// <summary>
///获取或设置此列按钮的背景颜色(如果有按钮的话)
/// </summary>
[Category("ObjectListView"),
Description("获取或设置此列按钮的背景颜色(如果有按钮的话)")]
public Color ButtonBaseColor
{
get;
set;
} = Color.Green;
/// <summary>
///获取或设置此列按钮的边框颜色(如果有按钮的话)
/// </summary>
[Category("ObjectListView"),
Description("获取或设置此列按钮的边框颜色(如果有按钮的话)")]
public Color ButtonBorderColor
{
get;
set;
} = Color.Gray;
/// <summary>
///获取或设置此列按钮的内边框颜色(如果有按钮的话)
/// </summary>
[Category("ObjectListView"),
Description("获取或设置此列按钮的内边框颜色(如果有按钮的话)")]
public Color ButtonInnerBorderColor
{
get;
set;
} = Color.Gray;
/// <summary>
///获取或设置此列按钮是否画边框(如果有按钮的话)
/// </summary>
[Category("ObjectListView"),
Description("获取或设置此列按钮是否画边框(如果有按钮的话)")]
public bool ButtonDrawBorder
{
get;
set;
} =true;
#endregion
#region Rendering
/// <summary>
/// Calculate the size of the contents
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <returns></returns>
protected override Size CalculateContentSize(Graphics g, Rectangle r) {
if (this.SizingMode == OLVColumn.ButtonSizingMode.CellBounds)
return r.Size;
if (this.SizingMode == OLVColumn.ButtonSizingMode.FixedBounds)
return this.ButtonSize ?? r.Size;
// Ok, SizingMode must be TextBounds. So figure out the size of the text
Size textSize = this.CalculateTextSize(g, this.GetText(), r.Width);
// Allow for padding and max width
textSize.Height += this.ButtonPaddingOrDefault.Height * 2;
textSize.Width += this.ButtonPaddingOrDefault.Width * 2;
textSize.Height = r.Size.Height + this.ButtonPaddingOrDefault.Height * 2;
textSize.Width = textSize.Width+ 20 + this.ButtonPaddingOrDefault.Width * 2;
if (this.MaxButtonWidth != -1 && textSize.Width > this.MaxButtonWidth)
textSize.Width = this.MaxButtonWidth;
if (textSize.Width < this.MinButtonWidth)
textSize.Width = this.MinButtonWidth;
return textSize;
}
/// <summary>
/// Draw the button
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
protected override void DrawImageAndText(Graphics g, Rectangle r) {
TextFormatFlags textFormatFlags = TextFormatFlags.HorizontalCenter |
TextFormatFlags.VerticalCenter |
TextFormatFlags.EndEllipsis |
TextFormatFlags.NoPadding |
TextFormatFlags.SingleLine |
TextFormatFlags.PreserveGraphicsTranslateTransform;
if (this.ListView.RightToLeftLayout)
textFormatFlags |= TextFormatFlags.RightToLeft;
string buttonText = GetText();
//g.DrawString(buttonText, this.Font,Brushes.Black,new PointF(r.X,r.Y));
if (!String.IsNullOrEmpty(buttonText))
{
var button_state = CalculatePushButtonState();
var baseColor = ButtonBaseColor;
if (button_state == PushButtonState.Normal)
{
baseColor = ButtonBaseColor;
}
else if (button_state == PushButtonState.Hot)
{
baseColor = Rendering.Util_GDI.GetColor(ButtonBaseColor, 0, -35, -24, -30);
}
else if (button_state == PushButtonState.Pressed)
{
baseColor = Rendering.Util_GDI.GetColor(ButtonBaseColor, 0, -35, -24, -9);
}
else if (button_state == PushButtonState.Disabled)
{
baseColor = SystemColors.ControlDark;
}
Rendering.Util_GDI.DrawButtonX(buttonText, g, r, this.ButtonForeColor, baseColor, ButtonBorderColor, ButtonInnerBorderColor, ryControls.RoundStyle.All, 5, ButtonDrawBorder);
//Rendering.Util_GDI.DrawRoundButton(buttonText, g, r, Rendering.buttonStyle.ButtonNormal);
//ButtonRenderer.DrawButton(g, r, buttonText, this.Font, textFormatFlags, false, CalculatePushButtonState());
}
}
/// <summary>
/// What part of the control is under the given point?
/// </summary>
/// <param name="g"></param>
/// <param name="hti"></param>
/// <param name="bounds"></param>
/// <param name="x"></param>
/// <param name="y"></param>
protected override void StandardHitTest(Graphics g, OlvListViewHitTestInfo hti, Rectangle bounds, int x, int y) {
Rectangle r = ApplyCellPadding(bounds);
if (r.Contains(x, y))
hti.HitTestLocation = HitTestLocation.Button;
}
/// <summary>
/// What is the state of the button?
/// </summary>
/// <returns></returns>
protected PushButtonState CalculatePushButtonState() {
if (!this.ListItem.Enabled && !this.Column.EnableButtonWhenItemIsDisabled)
return PushButtonState.Disabled;
if (this.IsButtonHot)
return ObjectListView.IsLeftMouseDown ? PushButtonState.Pressed : PushButtonState.Hot;
return PushButtonState.Normal;
}
/// <summary>
/// Is the mouse over the button?
/// </summary>
protected bool IsButtonHot {
get {
return this.IsCellHot && this.ListView.HotCellHitLocation == HitTestLocation.Button;
}
}
#endregion
}
}