2021-07-04 01:41:15 +00:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
using System.Windows.Forms;
|
|
|
|
|
|
using System.Drawing;
|
|
|
|
|
|
using System.Drawing.Drawing2D;
|
|
|
|
|
|
using Sheng.Winform.Controls.PopupControl;
|
|
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using Sheng.Winform.Controls.Kernal;
|
|
|
|
|
|
using Sheng.Winform.Controls.Drawing;
|
|
|
|
|
|
using System.ComponentModel;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Sheng.Winform.Controls
|
|
|
|
|
|
{
|
|
|
|
|
|
/*
|
|
|
|
|
|
* 控件的高度根据内容自动调整,忽略外部设置
|
|
|
|
|
|
*/
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 控件的高度根据内容自动调整,忽略外部设置
|
|
|
|
|
|
/// </summary>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public class ShengComboSelector2 : Control, IShengValidate
|
|
|
|
|
|
{
|
|
|
|
|
|
#region 私有成员
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 边框宽度
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
const int _borderSize = 1;
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* 下拉框弹出之后,再次点击控件,下拉框又会闪出来
|
|
|
|
|
|
* 是因为点控件,下拉框自动关闭,但因为点了控件,所以下拉框又会闪出来
|
|
|
|
|
|
* 所以在下拉框关闭时,记下时间,点击时判断与上次下拉框关闭时的时间间隔
|
|
|
|
|
|
* 如果过小,就不打开下拉框
|
|
|
|
|
|
*/
|
|
|
|
|
|
private DateTime _dropDownHideTime;
|
|
|
|
|
|
|
2021-07-29 09:09:27 +00:00
|
|
|
|
private readonly Popup _popup;
|
|
|
|
|
|
private readonly ShengListView _listView;
|
2021-07-04 01:41:15 +00:00
|
|
|
|
|
|
|
|
|
|
#region 当前状态
|
|
|
|
|
|
|
|
|
|
|
|
private ShengComboSelectorState _state = ShengComboSelectorState.None;
|
|
|
|
|
|
|
|
|
|
|
|
private bool Selected
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
return (_state & ShengComboSelectorState.Selected) == ShengComboSelectorState.Selected;
|
|
|
|
|
|
}
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
bool selected = Selected;
|
|
|
|
|
|
|
|
|
|
|
|
if (value)
|
2021-07-29 09:09:27 +00:00
|
|
|
|
_state |= ShengComboSelectorState.Selected;
|
2021-07-04 01:41:15 +00:00
|
|
|
|
else
|
2021-07-29 09:09:27 +00:00
|
|
|
|
_state ^= ShengComboSelectorState.Selected;
|
2021-07-04 01:41:15 +00:00
|
|
|
|
|
|
|
|
|
|
if (selected != Selected)
|
|
|
|
|
|
Refresh();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool Hovered
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
return (_state & ShengComboSelectorState.Hovered) == ShengComboSelectorState.Hovered;
|
|
|
|
|
|
}
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
bool hovered = Hovered;
|
|
|
|
|
|
|
|
|
|
|
|
if (value)
|
2021-07-29 09:09:27 +00:00
|
|
|
|
_state |= ShengComboSelectorState.Hovered;
|
2021-07-04 01:41:15 +00:00
|
|
|
|
else
|
2021-07-29 09:09:27 +00:00
|
|
|
|
_state ^= ShengComboSelectorState.Hovered;
|
2021-07-04 01:41:15 +00:00
|
|
|
|
|
|
|
|
|
|
if (hovered != Hovered)
|
|
|
|
|
|
Refresh();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 绘图表面
|
|
|
|
|
|
|
|
|
|
|
|
//箭头的大小是指的箭头的高度,宽度将是高度的2倍
|
2021-07-29 09:09:27 +00:00
|
|
|
|
private readonly int _buttonSize =6;
|
2021-07-04 01:41:15 +00:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 文本和description文本之间的间距
|
|
|
|
|
|
/// </summary>
|
2021-07-29 09:09:27 +00:00
|
|
|
|
private readonly int _textSpaceBetween = 2;
|
2021-07-04 01:41:15 +00:00
|
|
|
|
|
2021-07-29 09:09:27 +00:00
|
|
|
|
private readonly TextFormatFlags _textFlags = TextFormatFlags.SingleLine | TextFormatFlags.WordEllipsis;
|
2021-07-04 01:41:15 +00:00
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 公开属性
|
|
|
|
|
|
|
|
|
|
|
|
private int _maxItem = 5;
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 最大显示的项数
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public int MaxItem
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return _maxItem; }
|
|
|
|
|
|
set { _maxItem = value; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private ShengComboSelectorTheme _theme = new ShengComboSelectorTheme();
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public ShengComboSelectorTheme Theme
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return _theme; }
|
|
|
|
|
|
set { _theme = value; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool _showDescription = true;
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 是否显示说明字段,此属性不影响弹出面板是否显示
|
|
|
|
|
|
/// 弹出面板是否显示由DescriptionMember是否设置决定
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public bool ShowDescription
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return _showDescription; }
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
_showDescription = value;
|
|
|
|
|
|
this.Height = MeasureHeight();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public string DisplayMember
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return _listView.DisplayMember; }
|
|
|
|
|
|
set { _listView.DisplayMember = value; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private string _descriptionMember;
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public string DescriptionMember
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return _descriptionMember; }
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
_descriptionMember = value;
|
|
|
|
|
|
_listView.SetExtendMember(ShengListViewDescriptiveMembers.DescriptioinMember, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 下拉列表的布局模式
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public ShengListViewLayoutMode LayoutMode
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return _listView.LayoutMode; }
|
|
|
|
|
|
set { _listView.LayoutMode = value; }
|
|
|
|
|
|
}
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public override Font Font
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
return base.Font;
|
|
|
|
|
|
}
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
base.Font = value;
|
|
|
|
|
|
this.Height = MeasureHeight();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region 数据校验有关
|
|
|
|
|
|
|
|
|
|
|
|
private bool allowEmpty = true;
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 是否允许空
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[Description("是否允许空")]
|
|
|
|
|
|
[Category("Sheng.Winform.Controls")]
|
|
|
|
|
|
public bool AllowEmpty
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
return this.allowEmpty;
|
|
|
|
|
|
}
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
this.allowEmpty = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 构造
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public ShengComboSelector2()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (DesignMode)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
SetStyle(ControlStyles.ResizeRedraw, true);
|
|
|
|
|
|
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
|
|
|
|
|
|
SetStyle(ControlStyles.Selectable, true);
|
|
|
|
|
|
|
|
|
|
|
|
this.Padding = new System.Windows.Forms.Padding(5);
|
|
|
|
|
|
|
2021-07-29 09:09:27 +00:00
|
|
|
|
_listView = new ShengListView
|
|
|
|
|
|
{
|
|
|
|
|
|
Padding = new System.Windows.Forms.Padding(0),
|
|
|
|
|
|
BorderStyle = BorderStyle.None,
|
|
|
|
|
|
AllowMultiSelection = false,
|
|
|
|
|
|
LayoutMode = ShengListViewLayoutMode.Descriptive
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
//SelectedItemChanaged事件不行,如果就点当前已选中的项,就关不掉下拉框了
|
|
|
|
|
|
_listView.Click += new EventHandler(ListView_Click);
|
|
|
|
|
|
_listView.SelectedItemChanaged += new EventHandler(ListView_SelectedItemChanaged);
|
|
|
|
|
|
_listView.ItemTextGetting += new EventHandler<ShengListViewGetItemTextEventArgs>(ListView_ItemTextGetting);
|
2021-07-04 01:41:15 +00:00
|
|
|
|
|
|
|
|
|
|
_popup = new Popup(_listView);
|
2021-07-29 09:09:27 +00:00
|
|
|
|
_popup.Closed += new ToolStripDropDownClosedEventHandler(Popup_Closed);
|
2021-07-04 01:41:15 +00:00
|
|
|
|
|
|
|
|
|
|
this._dropDownHideTime = DateTime.UtcNow;
|
|
|
|
|
|
|
|
|
|
|
|
ApplyTheme();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 事件处理
|
|
|
|
|
|
|
2021-07-29 09:09:27 +00:00
|
|
|
|
void ListView_Click(object sender, EventArgs e)
|
2021-07-04 01:41:15 +00:00
|
|
|
|
{
|
|
|
|
|
|
_popup.Close();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-29 09:09:27 +00:00
|
|
|
|
void ListView_SelectedItemChanaged(object sender, EventArgs e)
|
2021-07-04 01:41:15 +00:00
|
|
|
|
{
|
|
|
|
|
|
object value = GetSelectedValue();
|
|
|
|
|
|
OnSelectedValueChanged(value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-29 09:09:27 +00:00
|
|
|
|
void ListView_ItemTextGetting(object sender, ShengListViewGetItemTextEventArgs e)
|
2021-07-04 01:41:15 +00:00
|
|
|
|
{
|
|
|
|
|
|
if (this.ItemTextGetting != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ItemTextGettingEventArgs args = new ItemTextGettingEventArgs(e.Item);
|
|
|
|
|
|
ItemTextGetting(this, args);
|
|
|
|
|
|
e.Text = args.Text;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-29 09:09:27 +00:00
|
|
|
|
void Popup_Closed(object sender, ToolStripDropDownClosedEventArgs e)
|
2021-07-04 01:41:15 +00:00
|
|
|
|
{
|
|
|
|
|
|
_dropDownHideTime = DateTime.UtcNow;
|
|
|
|
|
|
|
|
|
|
|
|
this.Selected = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 方法
|
|
|
|
|
|
|
|
|
|
|
|
#region private
|
|
|
|
|
|
|
|
|
|
|
|
private void ApplyTheme()
|
|
|
|
|
|
{
|
|
|
|
|
|
this.BackColor = _theme.BackgroundColor;
|
|
|
|
|
|
|
|
|
|
|
|
_listView.Theme.HoverColorStart = _theme.HoveredBackColor;
|
|
|
|
|
|
_listView.Theme.HoverColorEnd = Color.FromArgb(125, _theme.HoveredBackColor);
|
|
|
|
|
|
_listView.Theme.ItemBorderColor = _theme.HoveredBorderColor;
|
|
|
|
|
|
_listView.Theme.SelectedColorStart = _theme.SelectedBackColor;
|
|
|
|
|
|
_listView.Theme.SelectedColorEnd = Color.FromArgb(125, _theme.HoveredBackColor);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnSelectedValueChanged(object value)
|
|
|
|
|
|
{
|
|
|
|
|
|
//允许value为null
|
|
|
|
|
|
//从原来有选定的变为没选定了,事件肯定还是要触发的
|
|
|
|
|
|
if (this.SelectedValueChanged != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
SelectedValueChanged(this, new OnSelectedValueChangedEventArgs(value));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 测量当前控件应该的高度
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
private int MeasureHeight()
|
|
|
|
|
|
{
|
|
|
|
|
|
int textHeight = this.FontHeight;
|
|
|
|
|
|
|
|
|
|
|
|
int height = _borderSize * 2 + textHeight + Padding.Top + Padding.Bottom;
|
|
|
|
|
|
|
|
|
|
|
|
if (ShowDescription)
|
|
|
|
|
|
height = height + textHeight + _textSpaceBetween;
|
|
|
|
|
|
|
|
|
|
|
|
return height;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 整个可用的绘图表面
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private Rectangle GetBorderRectangle()
|
|
|
|
|
|
{
|
|
|
|
|
|
Rectangle rect = new Rectangle(this.ClientRectangle.Location, this.ClientRectangle.Size);
|
|
|
|
|
|
rect.Width--;
|
|
|
|
|
|
rect.Height--;
|
|
|
|
|
|
return rect;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Rectangle GetContentRectangle()
|
|
|
|
|
|
{
|
|
|
|
|
|
Rectangle contentRectangle = GetBorderRectangle();
|
|
|
|
|
|
//return Rectangle.Inflate(contentRectangle, -1, -1);
|
|
|
|
|
|
|
|
|
|
|
|
contentRectangle.X += 1;
|
|
|
|
|
|
contentRectangle.Y += 1;
|
|
|
|
|
|
contentRectangle.Width -= 1;
|
|
|
|
|
|
contentRectangle.Height -= 1;
|
|
|
|
|
|
|
|
|
|
|
|
return contentRectangle;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 右边按钮的绘图表面
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private Rectangle GetButtonAreaRectangle()
|
|
|
|
|
|
{
|
|
|
|
|
|
Rectangle clientRectangle = ClientRectangle;
|
|
|
|
|
|
|
2021-07-29 09:09:27 +00:00
|
|
|
|
Rectangle rectangle = new Rectangle
|
|
|
|
|
|
{
|
|
|
|
|
|
//箭头的大小是指的箭头的高度,宽度将是高度的2倍
|
|
|
|
|
|
Size = new Size(_buttonSize * 2, _buttonSize)
|
|
|
|
|
|
};
|
2021-07-04 01:41:15 +00:00
|
|
|
|
rectangle.Location = new Point(
|
|
|
|
|
|
clientRectangle.Width - rectangle.Width - Padding.Right,
|
|
|
|
|
|
(clientRectangle.Height - rectangle.Height) / 2);
|
|
|
|
|
|
return rectangle;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Rectangle GetTextRectangle()
|
|
|
|
|
|
{
|
|
|
|
|
|
Rectangle clientRectangle = GetContentRectangle();
|
|
|
|
|
|
Rectangle buttonRectangle = GetButtonAreaRectangle();
|
2021-07-29 09:09:27 +00:00
|
|
|
|
Rectangle textRectangle = new Rectangle
|
|
|
|
|
|
{
|
|
|
|
|
|
Location = new Point(Padding.Left, clientRectangle.Y + Padding.Top)
|
|
|
|
|
|
};
|
2021-07-04 01:41:15 +00:00
|
|
|
|
textRectangle.Width = clientRectangle.Width - (clientRectangle.Width - buttonRectangle.Left)
|
|
|
|
|
|
- textRectangle.Left - 5; //多减5为字和按钮的间距
|
|
|
|
|
|
textRectangle.Height = this.FontHeight;
|
|
|
|
|
|
|
|
|
|
|
|
return textRectangle;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Rectangle GetDescriptionRectangle()
|
|
|
|
|
|
{
|
|
|
|
|
|
Rectangle clientRectangle = GetContentRectangle();
|
|
|
|
|
|
Rectangle textRectangle = GetTextRectangle();
|
|
|
|
|
|
Rectangle buttonRectangle = GetButtonAreaRectangle();
|
|
|
|
|
|
Rectangle descriptionRectangle = new Rectangle();
|
|
|
|
|
|
|
|
|
|
|
|
int descriptionY = textRectangle.Y + textRectangle.Height + _textSpaceBetween;
|
|
|
|
|
|
descriptionRectangle.Location = new Point(Padding.Left, descriptionY);
|
|
|
|
|
|
descriptionRectangle.Width = clientRectangle.Width - (clientRectangle.Width - buttonRectangle.Left)
|
|
|
|
|
|
- descriptionRectangle.Left - 5; //多减5为字和按钮的间距
|
|
|
|
|
|
descriptionRectangle.Height = this.FontHeight;
|
|
|
|
|
|
|
|
|
|
|
|
return descriptionRectangle;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void Render(Graphics g)
|
|
|
|
|
|
{
|
|
|
|
|
|
DrawBorderground(g);
|
|
|
|
|
|
|
|
|
|
|
|
DrawBackground(g);
|
|
|
|
|
|
|
|
|
|
|
|
DrawText(g);
|
|
|
|
|
|
|
|
|
|
|
|
DrawButton(g);
|
|
|
|
|
|
|
|
|
|
|
|
//DrawFocusRectangle(g);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 绘制边框
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="g"></param>
|
|
|
|
|
|
private void DrawBorderground(Graphics g)
|
|
|
|
|
|
{
|
|
|
|
|
|
Rectangle borderRectangle = ClientRectangle;
|
|
|
|
|
|
|
|
|
|
|
|
//using (Pen borderPen = new Pen(Theme.BorderColor))
|
|
|
|
|
|
//{
|
|
|
|
|
|
// g.DrawRectangle(borderPen, borderRectangle);
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
Brush brush;
|
|
|
|
|
|
|
|
|
|
|
|
if (this.Selected)
|
|
|
|
|
|
brush = _theme.CreateSelectedBorderBrush(borderRectangle);
|
|
|
|
|
|
else if(this.Hovered)
|
|
|
|
|
|
brush = _theme.CreateHoveredBorderBrush(borderRectangle);
|
|
|
|
|
|
else
|
|
|
|
|
|
brush = _theme.CreateBorderBrush(borderRectangle);
|
|
|
|
|
|
|
|
|
|
|
|
g.FillRectangle(brush, borderRectangle);
|
|
|
|
|
|
|
|
|
|
|
|
Rectangle rectangle = borderRectangle;
|
|
|
|
|
|
rectangle.X += 1;
|
|
|
|
|
|
rectangle.Y += 1;
|
|
|
|
|
|
rectangle.Width -= 2;
|
|
|
|
|
|
rectangle.Height -= 2;
|
|
|
|
|
|
|
|
|
|
|
|
g.FillRectangle(Brushes.White, rectangle);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 绘制背景
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="g"></param>
|
|
|
|
|
|
private void DrawBackground(Graphics g)
|
|
|
|
|
|
{
|
|
|
|
|
|
Rectangle rectangle = GetContentRectangle();
|
|
|
|
|
|
|
|
|
|
|
|
if (this.Hovered && this.Selected == false)
|
|
|
|
|
|
{
|
|
|
|
|
|
//留出一个白色的内框
|
|
|
|
|
|
rectangle.X += 1;
|
|
|
|
|
|
rectangle.Y += 1;
|
|
|
|
|
|
rectangle.Width -= 2;
|
|
|
|
|
|
rectangle.Height -= 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-29 09:09:27 +00:00
|
|
|
|
Brush brush;
|
2021-07-04 01:41:15 +00:00
|
|
|
|
|
|
|
|
|
|
if (this.Enabled)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (this.Selected)
|
|
|
|
|
|
brush = _theme.CreateSelectedBackgroundBrush(rectangle);
|
|
|
|
|
|
else if (this.Hovered)
|
|
|
|
|
|
brush = _theme.CreateHoveredBackgroundBrush(rectangle);
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
//在正常状态下,还是要把指定的控件背景色考虑进去
|
|
|
|
|
|
//比如在验证控件数据的时候,不合法数据会有一个突出的颜色显示
|
|
|
|
|
|
//如现在会把不合法数据的控件背景色改成粉色
|
|
|
|
|
|
using (Brush normalBackgroundBrush = new SolidBrush(this.BackColor))
|
|
|
|
|
|
{
|
|
|
|
|
|
g.FillRectangle(normalBackgroundBrush, rectangle);
|
|
|
|
|
|
}
|
|
|
|
|
|
//这个CreateBackgroundBrush上面大部分是透明色,下面是一个淡灰色
|
|
|
|
|
|
brush = _theme.CreateBackgroundBrush(rectangle);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
brush = _theme.CreateDisabledBackgroundBrush(rectangle);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
g.FillRectangle(brush, rectangle);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 绘制文本
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="g"></param>
|
|
|
|
|
|
private void DrawText(Graphics g)
|
|
|
|
|
|
{
|
|
|
|
|
|
object selectedValue = GetSelectedValue();
|
|
|
|
|
|
if (selectedValue == null)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
#region 绘制标题文本
|
|
|
|
|
|
|
|
|
|
|
|
object textObj = ReflectionPool.GetPropertyValue(selectedValue, DisplayMember);
|
|
|
|
|
|
if (textObj != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
string text = textObj.ToString();
|
|
|
|
|
|
if (text != String.Empty)
|
|
|
|
|
|
{
|
|
|
|
|
|
Color textColor;
|
|
|
|
|
|
if (this.Selected)
|
|
|
|
|
|
textColor = _theme.SelectedTextColor;
|
|
|
|
|
|
else if (this.Hovered)
|
|
|
|
|
|
textColor = _theme.HoveredTextColor;
|
|
|
|
|
|
else
|
|
|
|
|
|
textColor = _theme.TextColor;
|
|
|
|
|
|
|
|
|
|
|
|
Font textFont = new System.Drawing.Font(this.Font, FontStyle.Bold);
|
|
|
|
|
|
Rectangle textRectangle = GetTextRectangle();
|
|
|
|
|
|
// g.FillRectangle(Brushes.Red, textRectangle);
|
|
|
|
|
|
TextRenderer.DrawText(g, text, textFont, textRectangle, textColor, _textFlags);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
if (this.ShowDescription)
|
|
|
|
|
|
{
|
|
|
|
|
|
#region 绘制Description
|
|
|
|
|
|
|
|
|
|
|
|
object descriptionObj = ReflectionPool.GetPropertyValue(selectedValue, DescriptionMember);
|
|
|
|
|
|
if (descriptionObj != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
string description = descriptionObj.ToString();
|
|
|
|
|
|
if (description != String.Empty)
|
|
|
|
|
|
{
|
|
|
|
|
|
Color textColor;
|
|
|
|
|
|
if (this.Selected)
|
|
|
|
|
|
textColor = _theme.SelectedDescriptionTextColor;
|
|
|
|
|
|
else if (this.Hovered)
|
|
|
|
|
|
textColor = _theme.HoveredDescriptionColor;
|
|
|
|
|
|
else
|
|
|
|
|
|
textColor = _theme.DescriptionTextColor;
|
|
|
|
|
|
|
|
|
|
|
|
Rectangle descriptionRectangle = GetDescriptionRectangle();
|
|
|
|
|
|
// g.FillRectangle(Brushes.Red, descriptionRectangle);
|
|
|
|
|
|
TextRenderer.DrawText(g, description, this.Font, descriptionRectangle, textColor, _textFlags);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void DrawButton(Graphics g)
|
|
|
|
|
|
{
|
|
|
|
|
|
Rectangle rectangle = GetButtonAreaRectangle();
|
|
|
|
|
|
// g.FillRectangle(Brushes.Red, rectangle);
|
|
|
|
|
|
|
|
|
|
|
|
int arrowX = rectangle.Left + (rectangle.Width - rectangle.Width / 2);
|
|
|
|
|
|
int arrowY = rectangle.Y;
|
|
|
|
|
|
Point startPoint = new Point(arrowX, arrowY);
|
|
|
|
|
|
Point endPoint = new Point(arrowX, arrowY + rectangle.Height);
|
|
|
|
|
|
GraphicsPath arrowPath = DrawingTool.GetArrowPath(startPoint, endPoint, rectangle.Height);
|
|
|
|
|
|
|
|
|
|
|
|
using (Brush arrowBrush = new LinearGradientBrush(rectangle,
|
|
|
|
|
|
_theme.ArrowColorStart, _theme.ArrowColorEnd, 45))
|
|
|
|
|
|
{
|
|
|
|
|
|
g.FillPath(arrowBrush, arrowPath);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
arrowPath.Dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void DrawFocusRectangle(Graphics g)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (this.Focused)
|
|
|
|
|
|
ControlPaint.DrawFocusRectangle(g, GetBorderRectangle());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region protected
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="e"></param>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
protected override void OnSizeChanged(EventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
this.Height = MeasureHeight();
|
|
|
|
|
|
base.OnSizeChanged(e);
|
|
|
|
|
|
}
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="pevent"></param>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
protected override void OnPaintBackground(PaintEventArgs pevent)
|
|
|
|
|
|
{
|
|
|
|
|
|
pevent.Graphics.Clear(_theme.BackgroundColor);
|
|
|
|
|
|
// base.OnPaintBackground(pevent);
|
|
|
|
|
|
}
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="e"></param>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
protected override void OnPaint(PaintEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
Render(e.Graphics);
|
|
|
|
|
|
}
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="e"></param>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
protected override void OnMouseEnter(EventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (this.Enabled)
|
|
|
|
|
|
{
|
|
|
|
|
|
this.Hovered = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
base.OnMouseEnter(e);
|
|
|
|
|
|
}
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="e"></param>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
protected override void OnMouseLeave(EventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (this.Enabled)
|
|
|
|
|
|
{
|
|
|
|
|
|
//if (_popup.Visible == false)
|
|
|
|
|
|
//{
|
|
|
|
|
|
this.Hovered = false;
|
|
|
|
|
|
//}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
base.OnMouseLeave(e);
|
|
|
|
|
|
}
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="e"></param>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
protected override void OnEnter(EventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
//this.Selected = true;
|
|
|
|
|
|
base.OnEnter(e);
|
|
|
|
|
|
}
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="e"></param>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
protected override void OnLeave(EventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
//this.Selected = false;
|
|
|
|
|
|
base.OnLeave(e);
|
|
|
|
|
|
}
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="e"></param>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
protected override void OnGotFocus(EventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
this.Refresh();
|
|
|
|
|
|
base.OnGotFocus(e);
|
|
|
|
|
|
}
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="e"></param>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
protected override void OnLostFocus(EventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
this.Refresh();
|
|
|
|
|
|
base.OnLostFocus(e);
|
|
|
|
|
|
}
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="e"></param>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
protected override void OnMouseDown(MouseEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
base.OnMouseDown(e);
|
|
|
|
|
|
|
|
|
|
|
|
this.Focus();
|
|
|
|
|
|
|
|
|
|
|
|
if ((DateTime.UtcNow - _dropDownHideTime).TotalSeconds > 0.2)
|
|
|
|
|
|
{
|
|
|
|
|
|
Selected = true;
|
|
|
|
|
|
ShowDropDown();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ShowDropDown()
|
|
|
|
|
|
{
|
|
|
|
|
|
//每次打开都要判读,如果把PopupWidth做成属性,在set里设置
|
|
|
|
|
|
//那么控件resize时,popup的宽度可能就不对了
|
|
|
|
|
|
_popup.Width = this.Width;
|
|
|
|
|
|
|
2021-07-29 09:09:27 +00:00
|
|
|
|
int showItem;
|
2021-07-04 01:41:15 +00:00
|
|
|
|
int itemCount = _listView.Items.Count;
|
|
|
|
|
|
|
|
|
|
|
|
if (itemCount > this.MaxItem)
|
|
|
|
|
|
showItem = this.MaxItem;
|
|
|
|
|
|
else if (itemCount == 0)
|
|
|
|
|
|
showItem = 1;
|
|
|
|
|
|
else
|
|
|
|
|
|
showItem = itemCount;
|
|
|
|
|
|
|
|
|
|
|
|
_popup.Height = showItem * _listView.ItemHeight;
|
|
|
|
|
|
|
|
|
|
|
|
_popup.Height = _listView.Height;
|
|
|
|
|
|
|
|
|
|
|
|
_popup.Show(this);
|
|
|
|
|
|
|
|
|
|
|
|
_popup.Select();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region public
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="dataSource"></param>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public void DataBind(IList dataSource)
|
|
|
|
|
|
{
|
|
|
|
|
|
_listView.DataBind(dataSource);
|
|
|
|
|
|
this.Refresh();
|
|
|
|
|
|
}
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public object GetSelectedValue()
|
|
|
|
|
|
{
|
|
|
|
|
|
return _listView.GetSelectedValue();
|
|
|
|
|
|
}
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="obj"></param>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public void SetSelectedValue(object obj)
|
|
|
|
|
|
{
|
|
|
|
|
|
_listView.SetSelectedValue(obj);
|
|
|
|
|
|
}
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public void Clear()
|
|
|
|
|
|
{
|
|
|
|
|
|
_listView.Clear();
|
|
|
|
|
|
this.Refresh();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 公开事件
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当前热点项生改变
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public event EventHandler<OnSelectedValueChangedEventArgs> SelectedValueChanged;
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public class OnSelectedValueChangedEventArgs:EventArgs
|
|
|
|
|
|
{
|
2021-07-29 09:09:27 +00:00
|
|
|
|
private readonly object _value;
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public object Value
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return _value; }
|
|
|
|
|
|
}
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="value"></param>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public OnSelectedValueChangedEventArgs(object value)
|
|
|
|
|
|
{
|
|
|
|
|
|
_value = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 通过外能事件获取用于绘制项的文本
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public event EventHandler<ItemTextGettingEventArgs> ItemTextGetting;
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public class ItemTextGettingEventArgs : EventArgs
|
|
|
|
|
|
{
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public object Item { get; private set; }
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public string Text { get; set; }
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="item"></param>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public ItemTextGettingEventArgs(object item)
|
|
|
|
|
|
{
|
|
|
|
|
|
Item = item;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region ISEValidate 成员
|
|
|
|
|
|
|
|
|
|
|
|
private string title;
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 标题
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[Description("标题")]
|
|
|
|
|
|
[Category("Sheng.Winform.Controls")]
|
|
|
|
|
|
public string Title
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
return this.title;
|
|
|
|
|
|
}
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
this.title = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool highLight = true;
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 验证失败时是否需要高亮显示(改变背景色)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[Description("验证失败时是否需要高亮显示(改变背景色)")]
|
|
|
|
|
|
[Category("Sheng.Winform.Controls")]
|
|
|
|
|
|
public bool HighLight
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
return this.highLight;
|
|
|
|
|
|
}
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
this.highLight = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="msg"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public bool SEValidate(out string msg)
|
|
|
|
|
|
{
|
|
|
|
|
|
msg = String.Empty;
|
|
|
|
|
|
|
|
|
|
|
|
if (this.AllowEmpty == false && this.GetSelectedValue() == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
msg += String.Format("[ {0} ] {1}", this.Title, "不允许为空");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region CustomValidate
|
|
|
|
|
|
|
|
|
|
|
|
if (CustomValidate != null)
|
|
|
|
|
|
{
|
2021-07-29 09:09:27 +00:00
|
|
|
|
if (CustomValidate(this, out string customValidateMsg) == false)
|
2021-07-04 01:41:15 +00:00
|
|
|
|
{
|
|
|
|
|
|
msg += String.Format("[ {0} ] {1}", this.Title, customValidateMsg);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2021-07-29 09:09:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
2021-07-04 01:41:15 +00:00
|
|
|
|
public CustomValidateMethod CustomValidate
|
|
|
|
|
|
{
|
|
|
|
|
|
get;
|
|
|
|
|
|
set;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|