Данный контрол (с возможными незначительными изменениями) добавлен в библиотеку Net.KSUniLib с версии 1.0.3.19.
Namespace: KomeSoftUniCL.Controls.DataBases
Class: KSDBSelectTextBox
Сегодня мы будем создавать интересный контрол, представляющий собой TextBox и встроенной кнопкой:
Такие контролы используются, в основном, в программах, работающих с базами данных. Однако, путем универсализации его функционала, его ареал использования можно существенно расширить.
Итак, что нам нужно от такого контрола?
1) Открытие какой-либо формы в модальном режиме. Какая форма будет открываться, контрол не знает, и какие данные форма будет возвращать, контрол тоже представлять не будет.
2) Вызов диалога открытия файла. Да, реализовать подобный функционал в коде на C# несложно, и примеров в сети по данному вопросу гора. Но согласитесь, если тоже самое можно сделать установкой одного свойства в контроле - это удобно.
3) Вызов диалога сохранения файла. Аргументы те же, что и в п.2.
4) Вызов диалога выбора директории.
Кроме того, мы хотим иметь возможность в коде формы, на которой расположен данный контрол, управлять его выполнением. Например, интерактивно отключить выполнение события выбора без замораживания самого контрола, и т.д.
Иии, начнемс...
Создаем новый User Control. Я, для простоты, буду называть все классы, свойства и т.д. теми же именами, которые использовал в библиотеке. Вы можете использовать любые собственные имена.
Для начала, в режиме дизайнера форм рисуем контрол. Я в работе использую не Visual Studio, а SharpDevelop, поэтому внешний вид каких-то элементов среды может отличаться от таковых в VS, но, я думаю, в целом все достаточно похоже.
Нам необходимо стандартный холст UserControl растянуть так, как будет в итоге выглядеть наш контрол:
Далее, нам нужно разместить на холсте стандартный TextBox со свойством Dock равным Fill. TextBox займет собой весь холст по ширине, а по высоте - насколько позволит высота строки. Настраиваем TextBox по своему вкусу, устанавливаем тип рамки, шрифт, кегль, цвет текста и т.д.
Затем размещаем на холсте поверх TextBox кнопку или, как в моем случае, Label с установленным значением рамки, заливкой фона, текстом "..." и свойством Cursor со значением Hand. Получится та же кнопка. Мне вариант с Label нравится больше, субъективно лучше выглядит. В итоге получаем следующее:
Обзываем TextBox - CompoentTB, а Label - ComponentLB.
У Label-кнопки необходимо установить свойство Anchor в значения Top и Right, чтобы кнопка была жестко привязана к верхнему правому углу и не двигалась при изменении размера контрола.
Так же необходимо установить у кнопки значения MaximumSize и MinimumSize в значения, равные Size, чтобы она ни при каких обстоятельствах не меняла размер.
По сути, визуальная составляющая контрола готова. Переходим в код.
Перво-наперво, я хочу, чтобы моя кнопка визуально реагировала на нажатия мышью. Пишем в коде конструктора класса обработчики событий MouseDown и MouseUp в виде простых лямбда-выражений:
Т.е., при нажатии кнопки мыши цвет Label устанавливается в LightSalmon, а при отпускании - в Snow. Теперь Label стал совсем как настоящая кнопка.
Теперь я хочу вынести из контрола кое-какие параметры встроенного TextBox. В частности, я хочу, чтобы можно было при создании контрола установить значения свойств Multiline и ReadOnly TextBox'a. Создаем свойства класса контрола:
Небольшое отступление по использованию атрибутов в данном коде. Поскольку мы создаем визуальный компонент, нам необходимо сделать так, чтобы некоторые особо важные свойства контрола мы могли редактировать не в коде, а в режиме дизайнера форм. Для этого перед необходимыми свойствами мы указываем атрибуты Description (устанавливает описание свойства в PropertyGrid дизайнера форм) и Category (в котором указываем, к какой категории необходимо отнести свойство). Если кастомных свойств у нас немного (в данном контроле их будет всего четыре), удобно создать свою категорию, которая, за счет первого символа подчеркивания, еще и выводится в PropertyGrid первой. Удобно!
После создания необходимых свойств нам нужно каким-то образом применять устанавливаемые в них значения к встроенному TextBox. Создадим универсальный метод обновления TextBox:
Каждый раз при вызове данного метода свойства Multiline и ReadOnly встроенного TextBox устанавливаются равными соответствующим им свойствам контрола. Кроме того, поскольку при установке ReadOnly в true TextBox автоматически меняет заливку на серую. В методе мы насильно перекрашиваем TextBox каждый раз обратно в белый. Можно сделать и по другому, но пока и так сойдет. Далее, мы вызываем метод каждый раз, когда необходимо обновить состояние TextBox:
Настало время закончить с тюнингом и начать претворять в жизнь самый главный функционал. Для начала нам нужно свойство, в котором мы можем указать, какой из режимов работы контрола нам необходим. Создадим перечисление:
Соответственно, используя каждый из элементов перечисления, мы направляем контрол по одному из путей реализации функционала. Создадим свойство с типом данных ActionType:
Реализуем выполнение контрола с Action = ActionType.SelectObject. По логике действий, контрол должен открыть какую-то форму, подцепиться к событию FormClosing, получить какой-то объект и вывести строковое представление полученного объекта в текстовое поле контрола. Создадим два новых свойства, на этот раз - неинтерактивных:
и внесем небольшое изменение в метод ReloadComponent():
Логика такова - при перезагрузке контрола в ComponentTB.Text записываем строковое представление SelectedObject, либо, если SelectedObject не установлен - то записываем пустую строку.
Все готово для обработки главного события контрола - выбора через нажатие на кнопку. Сначала создадим метод-обработчик:
Логика такова: если свойство SelectForm установлено, то открывается форма. Какая форма будет открыта, контрол не знает. К событию FormClosing подключаем лямбда-выражение, которое перед закрытием дочерней формы проверяет у нее свойство Tag и, если оно установлено, то устанавливает его значение в значение свойства SelectedObject контрола. Таким образом, дочерняя форма должна возвращать в свойстве Tag какой-то объект. Как это сделать, мы увидим в самом конце.
Осталось только подключить метод Select в качестве обработчика события Click кнопки:
На данном этапе контрол уже умеет открывать дочернюю форму и получать от нее какой-то объект. Однако этого мало, необходимо расширить функционал для поддержки всех типов действий. Создадим интерактивное свойство Filter для действий OpenFile и SaveFile:
Расширим метод Select() для поддержки всех необходимых действий:
Самое основное мы сделали. Мы создали четыре интерактивных свойства, и обработку нажатия на кнопку. Осталось совсем немного: создать два события, одно из которых должно выполняться до основного обработчика, а второе - после. Причем событие, выполняющееся до основного обработчика, должно уметь остановить его выполнение.
Метод-инициатор события BeforeSelect возвращает булево значение, указывающее основному обработчику, выполняться ему или нет. Внесем изменения в основной обработчик:
Таким образом, если OnBeforeSelect() вернет true, основной обработчик выполняться не будет.
Все, контрол успешно закончен. Можно проверять на форме. Создадим форму и четыре наших новых контрола: ksSelectObject, ksOpenFile, ksSaveFile и ksSelectDirectory:
В соответствии с названиями устанавливаем каждому из контролов значения Action.
Для второго и третьего контролов в дизайнере задаем значение свойства Filter сообразно с тем, как это свойство задается при вызове OpenFileDialog и SaveFileDialog.
Для четвертого контрола необходимо указать только тип действия. Проверим:
OpenFile - работает!
SaveFile - работает!
SelectDirectory - работает!
Осталось проверить первый контрол. Для этого создадим дополнительный класс и дочернюю форму:
На дочернюю форму разместим четыре LinkLabel для примера:
В обработчике события Load дочерней формы создадим четыре объекта нашего проверочного класса SimpleClass и пропишем обработчики Click:
Остается только указать первому контролу, что ему нужно открывать форму ChildForm и все:
Иии... Все работает!
Таким образом, путем не слишком сложных манипуляций мы получили многофункциональный универсальный контрол для баз данных и многих других приложений
Namespace: KomeSoftUniCL.Controls.DataBases
Class: KSDBSelectTextBox
Сегодня мы будем создавать интересный контрол, представляющий собой TextBox и встроенной кнопкой:
Такие контролы используются, в основном, в программах, работающих с базами данных. Однако, путем универсализации его функционала, его ареал использования можно существенно расширить.
Итак, что нам нужно от такого контрола?
1) Открытие какой-либо формы в модальном режиме. Какая форма будет открываться, контрол не знает, и какие данные форма будет возвращать, контрол тоже представлять не будет.
2) Вызов диалога открытия файла. Да, реализовать подобный функционал в коде на C# несложно, и примеров в сети по данному вопросу гора. Но согласитесь, если тоже самое можно сделать установкой одного свойства в контроле - это удобно.
3) Вызов диалога сохранения файла. Аргументы те же, что и в п.2.
4) Вызов диалога выбора директории.
Кроме того, мы хотим иметь возможность в коде формы, на которой расположен данный контрол, управлять его выполнением. Например, интерактивно отключить выполнение события выбора без замораживания самого контрола, и т.д.
Иии, начнемс...
Создаем новый User Control. Я, для простоты, буду называть все классы, свойства и т.д. теми же именами, которые использовал в библиотеке. Вы можете использовать любые собственные имена.
Для начала, в режиме дизайнера форм рисуем контрол. Я в работе использую не Visual Studio, а SharpDevelop, поэтому внешний вид каких-то элементов среды может отличаться от таковых в VS, но, я думаю, в целом все достаточно похоже.
Нам необходимо стандартный холст UserControl растянуть так, как будет в итоге выглядеть наш контрол:
Далее, нам нужно разместить на холсте стандартный TextBox со свойством Dock равным Fill. TextBox займет собой весь холст по ширине, а по высоте - насколько позволит высота строки. Настраиваем TextBox по своему вкусу, устанавливаем тип рамки, шрифт, кегль, цвет текста и т.д.
Затем размещаем на холсте поверх TextBox кнопку или, как в моем случае, Label с установленным значением рамки, заливкой фона, текстом "..." и свойством Cursor со значением Hand. Получится та же кнопка. Мне вариант с Label нравится больше, субъективно лучше выглядит. В итоге получаем следующее:
Обзываем TextBox - CompoentTB, а Label - ComponentLB.
У Label-кнопки необходимо установить свойство Anchor в значения Top и Right, чтобы кнопка была жестко привязана к верхнему правому углу и не двигалась при изменении размера контрола.
Так же необходимо установить у кнопки значения MaximumSize и MinimumSize в значения, равные Size, чтобы она ни при каких обстоятельствах не меняла размер.
По сути, визуальная составляющая контрола готова. Переходим в код.
Перво-наперво, я хочу, чтобы моя кнопка визуально реагировала на нажатия мышью. Пишем в коде конструктора класса обработчики событий MouseDown и MouseUp в виде простых лямбда-выражений:
#region CONSTRUCTORS
public KSDBSelectTextBox()
{
InitializeComponent();
ComponentLB.MouseDown += (object sender, MouseEventArgs e) => ComponentLB.BackColor = Color.LightSalmon;
ComponentLB.MouseUp += (object sender, MouseEventArgs e) => ComponentLB.BackColor = Color.Snow;
}
#endregion
Т.е., при нажатии кнопки мыши цвет Label устанавливается в LightSalmon, а при отпускании - в Snow. Теперь Label стал совсем как настоящая кнопка.
Теперь я хочу вынести из контрола кое-какие параметры встроенного TextBox. В частности, я хочу, чтобы можно было при создании контрола установить значения свойств Multiline и ReadOnly TextBox'a. Создаем свойства класса контрола:
#region PRIVATE
private Boolean allowMultiline = false;
private Boolean readOnly = true;
#endregion
#region PUBLIC
[Description("Указывает, используется ли Multiline во встроенном TextBox"),Category("_KSProperty")]
public Boolean AllowMultiline {
get {
return allowMultiline;
}
set {
allowMultiline = value;
}
}
[Description("Указывает, разрешено ли редактировать встроенный TextBox"),Category("_KSProperty")]
public Boolean ReadOnly {
get {
return readOnly;
}
set {
readOnly = value;
}
}
#endregion
Небольшое отступление по использованию атрибутов в данном коде. Поскольку мы создаем визуальный компонент, нам необходимо сделать так, чтобы некоторые особо важные свойства контрола мы могли редактировать не в коде, а в режиме дизайнера форм. Для этого перед необходимыми свойствами мы указываем атрибуты Description (устанавливает описание свойства в PropertyGrid дизайнера форм) и Category (в котором указываем, к какой категории необходимо отнести свойство). Если кастомных свойств у нас немного (в данном контроле их будет всего четыре), удобно создать свою категорию, которая, за счет первого символа подчеркивания, еще и выводится в PropertyGrid первой. Удобно!
После создания необходимых свойств нам нужно каким-то образом применять устанавливаемые в них значения к встроенному TextBox. Создадим универсальный метод обновления TextBox:
#region METHODS
public void ReloadComponentTB(){
ComponentTB.Multiline = this.AllowMultiline;
ComponentTB.ReadOnly = this.ReadOnly;
ComponentTB.BackColor = Color.White;
}
#endregion
Каждый раз при вызове данного метода свойства Multiline и ReadOnly встроенного TextBox устанавливаются равными соответствующим им свойствам контрола. Кроме того, поскольку при установке ReadOnly в true TextBox автоматически меняет заливку на серую. В методе мы насильно перекрашиваем TextBox каждый раз обратно в белый. Можно сделать и по другому, но пока и так сойдет. Далее, мы вызываем метод каждый раз, когда необходимо обновить состояние TextBox:
[Description("Указывает, используется ли Multiline во встроенном TextBox"),Category("_KSProperty")]
public Boolean AllowMultiline {
get {
return allowMultiline;
}
set {
allowMultiline = value;
ReloadComponentTB();
}
}
[Description("Указывает, разрешено ли редактировать встроенный TextBox"),Category("_KSProperty")]
public Boolean ReadOnly {
get {
return readOnly;
}
set {
readOnly = value;
ReloadComponentTB();
}
}
Настало время закончить с тюнингом и начать претворять в жизнь самый главный функционал. Для начала нам нужно свойство, в котором мы можем указать, какой из режимов работы контрола нам необходим. Создадим перечисление:
#region ENUMS
public enum ActionType{
SelectObject,
OpenFile,
SaveFile,
SelectDirectory
}
#endregion
Соответственно, используя каждый из элементов перечисления, мы направляем контрол по одному из путей реализации функционала. Создадим свойство с типом данных ActionType:
#region PRIVATE
private ActionType action = ActionType.SelectObject;
#endregion
#region PUBLIC
[Description("Указывает тип действия компонента"),Category("_KSProperty")]
public ActionType Action {
get {
return action;
}
set {
action = value;
}
}
#endregion
Реализуем выполнение контрола с Action = ActionType.SelectObject. По логике действий, контрол должен открыть какую-то форму, подцепиться к событию FormClosing, получить какой-то объект и вывести строковое представление полученного объекта в текстовое поле контрола. Создадим два новых свойства, на этот раз - неинтерактивных:
#region PRIVATE
private Form selectForm = null;
private Object selectedObject = null;
#endregion
#region PUBLIC
public Form SelectForm {
get {
return selectForm;
}
set {
selectForm = value;
}
}
public Object SelectedObject {
get {
return selectedObject;
}
set {
selectedObject = value;
ReloadComponentTB();
}
}
#endregion
и внесем небольшое изменение в метод ReloadComponent():
#region METHODS
public void ReloadComponentTB(){
ComponentTB.Multiline = this.AllowMultiline;
ComponentTB.ReadOnly = this.ReadOnly;
ComponentTB.BackColor = Color.White;
if(this.SelectedObject != null){
ComponentTB.Text = this.SelectedObject.ToString();
}
else{
ComponentTB.Text = String.Empty;
}
}
#endregion
Логика такова - при перезагрузке контрола в ComponentTB.Text записываем строковое представление SelectedObject, либо, если SelectedObject не установлен - то записываем пустую строку.
Все готово для обработки главного события контрола - выбора через нажатие на кнопку. Сначала создадим метод-обработчик:
#region METHODS
private void Select(){
switch(this.Action){
case ActionType.SelectObject:
if(this.SelectForm != null){
this.SelectForm.FormClosing += (sender, e) => {
if(this.SelectForm.Tag != null){
this.SelectedObject = this.SelectForm.Tag;
}
};
this.SelectForm.ShowDialog();
}
break;
}
}
#endregion
Логика такова: если свойство SelectForm установлено, то открывается форма. Какая форма будет открыта, контрол не знает. К событию FormClosing подключаем лямбда-выражение, которое перед закрытием дочерней формы проверяет у нее свойство Tag и, если оно установлено, то устанавливает его значение в значение свойства SelectedObject контрола. Таким образом, дочерняя форма должна возвращать в свойстве Tag какой-то объект. Как это сделать, мы увидим в самом конце.
Осталось только подключить метод Select в качестве обработчика события Click кнопки:
#region CONSTRUCTORS
public KSDBSelectTextBox()
{
InitializeComponent();
ComponentLB.MouseDown += (object sender, MouseEventArgs e) => ComponentLB.BackColor = Color.LightSalmon;
ComponentLB.MouseUp += (object sender, MouseEventArgs e) => ComponentLB.BackColor = Color.Snow;
ComponentLB.Click += (sender, e) => this.Select();
ReloadComponentTB();
}
#endregion
На данном этапе контрол уже умеет открывать дочернюю форму и получать от нее какой-то объект. Однако этого мало, необходимо расширить функционал для поддержки всех типов действий. Создадим интерактивное свойство Filter для действий OpenFile и SaveFile:
#region PRIVATE
private String filter = String.Empty;
#endregion
#region PUBLIC
[Description("Указывает фильтр для действий OpenFile и SaveFile"),Category("_KSProperty")]
public String Filter {
get {
return filter;
}
set {
filter = value;
}
}
#endregion
Расширим метод Select() для поддержки всех необходимых действий:
private void Select(){
switch(this.Action){
case ActionType.SelectObject:
if(this.SelectForm != null){
this.SelectForm.FormClosing += (sender, e) => {
if(this.SelectForm.Tag != null){
this.SelectedObject = this.SelectForm.Tag;
}
};
this.SelectForm.ShowDialog();
}
break;
case ActionType.OpenFile:
OpenFileDialog OFD = new OpenFileDialog();
OFD.Filter = this.Filter;
if(OFD.ShowDialog() == DialogResult.OK){
this.SelectedObject = OFD.FileName;
}
break;
case ActionType.SaveFile:
SaveFileDialog SFD = new SaveFileDialog();
SFD.Filter = this.Filter;
if(SFD.ShowDialog() == DialogResult.OK){
this.SelectedObject = SFD.FileName;
}
break;
case ActionType.SelectDirectory:
FolderBrowserDialog FBD = new FolderBrowserDialog();
if(FBD.ShowDialog() == DialogResult.OK){
this.SelectedObject = FBD.SelectedPath;
}
break;
}
}
Самое основное мы сделали. Мы создали четыре интерактивных свойства, и обработку нажатия на кнопку. Осталось совсем немного: создать два события, одно из которых должно выполняться до основного обработчика, а второе - после. Причем событие, выполняющееся до основного обработчика, должно уметь остановить его выполнение.
#region EVENTS
public delegate void BeforeSelectHandler(object sender, CancelEventArgs args);
[Description("Событие, возникающее до выбора"),Category("_KSAction")]
public event BeforeSelectHandler BeforeSelect;
public Boolean OnBeforeSelect(){
Boolean result = false;
if(BeforeSelect != null){
CancelEventArgs args = new CancelEventArgs();
BeforeSelect(this, args);
result = args.Cancel;
}
return result;
}
public delegate void AfterSelectHandler(object sender, EventArgs args);
[Description("Событие, возникающее после выбора"),Category("_KSAction")]
public event AfterSelectHandler AfterSelect;
public void OnAfterSelect(){
if(AfterSelect != null){
AfterSelect(this, new EventArgs());
}
}
#endregion
Метод-инициатор события BeforeSelect возвращает булево значение, указывающее основному обработчику, выполняться ему или нет. Внесем изменения в основной обработчик:
#region METHODS
private void Select(){
if(this.OnBeforeSelect()){
return;
}
switch(this.Action){
case ActionType.SelectObject:
if(this.SelectForm != null){
this.SelectForm.FormClosing += (sender, e) => {
if(this.SelectForm.Tag != null){
this.SelectedObject = this.SelectForm.Tag;
}
};
this.SelectForm.ShowDialog();
}
break;
case ActionType.OpenFile:
OpenFileDialog OFD = new OpenFileDialog();
OFD.Filter = this.Filter;
if(OFD.ShowDialog() == DialogResult.OK){
this.SelectedObject = OFD.FileName;
}
break;
case ActionType.SaveFile:
SaveFileDialog SFD = new SaveFileDialog();
SFD.Filter = this.Filter;
if(SFD.ShowDialog() == DialogResult.OK){
this.SelectedObject = SFD.FileName;
}
break;
case ActionType.SelectDirectory:
FolderBrowserDialog FBD = new FolderBrowserDialog();
if(FBD.ShowDialog() == DialogResult.OK){
this.SelectedObject = FBD.SelectedPath;
}
break;
}
this.OnAfterSelect();
}
#endregion
Таким образом, если OnBeforeSelect() вернет true, основной обработчик выполняться не будет.
Все, контрол успешно закончен. Можно проверять на форме. Создадим форму и четыре наших новых контрола: ksSelectObject, ksOpenFile, ksSaveFile и ksSelectDirectory:
В соответствии с названиями устанавливаем каждому из контролов значения Action.
Для второго и третьего контролов в дизайнере задаем значение свойства Filter сообразно с тем, как это свойство задается при вызове OpenFileDialog и SaveFileDialog.
Для четвертого контрола необходимо указать только тип действия. Проверим:
OpenFile - работает!
SaveFile - работает!
SelectDirectory - работает!
Осталось проверить первый контрол. Для этого создадим дополнительный класс и дочернюю форму:
public class SimpleClass
{
private Int32 simpleID = 0;
private String simpleProp = String.Empty;
public Int32 SimpleID {
get {
return simpleID;
}
set {
simpleID = value;
}
}
public String SimpleProp {
get {
return simpleProp;
}
set {
simpleProp = value;
}
}
public SimpleClass(Int32 _id, String _prop)
{
this.SimpleID = _id;
this.SimpleProp = _prop;
}
public override string ToString()
{
return String.Format("Объект #{0}. Имя {1}", this.SimpleID, this.SimpleProp);
}
}
На дочернюю форму разместим четыре LinkLabel для примера:
В обработчике события Load дочерней формы создадим четыре объекта нашего проверочного класса SimpleClass и пропишем обработчики Click:
public partial class ChildForm : Form
{
public ChildForm()
{
//
// The InitializeComponent() call is required for Windows Forms designer support.
//
InitializeComponent();
//
// TODO: Add constructor code after the InitializeComponent() call.
//
}
void ClickHandler(SimpleClass objResult){
this.Tag = objResult;
this.Close();
}
void ChildFormLoad(object sender, EventArgs e)
{
SimpleClass objOne = new SimpleClass(1, "Первый объект");
SimpleClass objTwo = new SimpleClass(2, "Второй объект");
SimpleClass objThree = new SimpleClass(3, "Третий объект");
SimpleClass objFour = new SimpleClass(4, "Четвертый объект");
linkLabel1.Text = objOne.ToString();
linkLabel1.Click += (object objSender, EventArgs objE) => ClickHandler(objOne);
linkLabel2.Text = objTwo.ToString();
linkLabel2.Click += (object objSender, EventArgs objE) => ClickHandler(objTwo);
linkLabel3.Text = objThree.ToString();
linkLabel3.Click += (object objSender, EventArgs objE) => ClickHandler(objThree);
linkLabel4.Text = objFour.ToString();
linkLabel4.Click += (object objSender, EventArgs objE) => ClickHandler(objFour);
}
}
Остается только указать первому контролу, что ему нужно открывать форму ChildForm и все:
ksSelectObject.SelectForm = new ChildForm();
Иии... Все работает!
Таким образом, путем не слишком сложных манипуляций мы получили многофункциональный универсальный контрол для баз данных и многих других приложений
Комментариев нет:
Отправить комментарий