Главная » C#, Компоненты » Потоки в C# правила использования

0

Часто длительные операции, такие как поиск файлов, анализ данных и т. п., выносят в отдельный поток (thread). Если же при этом нужно отображать прогресс выполнения операции, то возникает проблема доступа к компонентам (например, к компоненту progressBar) из одного или нескольких потоков, с которой мы попробуем разобраться в этом разделе.

В принципе можно просто воспользоваться компонентом Backgroundworker, который позволяет запускать длительные операции в отдельном потоке и получать статус выполнения этих операций. В листинге 12.10 представлен небольшой пример использования этого компонента. Напрямую к разработке компонентов эта тема не относится, поэтому мы не будем долго останавливаться на работе с этим компонентом, но сам пример нам еще пригодится.

Основное правило, о котором следует помнить при разработке потоков,— доступ к компонентам не является потокобезопасным! Посмотрите, что происходит, если в листинге 12.10 я попытаюсь установить значение progressBarl прямо из потока вычисления:

private int Calculation(BackgroundWorker bw, int arg) {

double progress = result * 100 / LEN; progressBarl.Value = Convert.ToInt32(progress); progressBarl.Invalidate{); ;

}

Если запустить получившееся приложение без отладчика, все будет работать так, как положено. А вот в режиме отладки Visual Studio выдаст исключение invaiidOperationException (рис. 12.6), сообщающее о том, что была произведена попытка доступа к компоненту из другого потока: "Control ‘progressBarl’ accessed from a thread other than the thread it was created on". На самом деле, и в режиме выполнения такой код будет хранить потенциальную ошибку, которая может возникнуть в любой момент. Вызов исключения в режиме отладки можно заблокировать с помощью специального флага: System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls= false;

Но только для отладки! Правильный путь— это исправить потенциальную проблему.

Листинг 12 10 Использование компонента UackgioundWorker

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System. Windows.Forms;

using System.Threading;

namespace BackgroundWorkerExample {

public partial class Forml : Form {

public Forml() {

InitializeComponent(};

private void btnStart_Click(object sender, EventArgs e)

{

// Запускаем операцию с аргументом.

// Если не надо, то аргумент можно не передавать.

this.backgroundWorke г1.RunWorkerAsync(20000};

}

private void btbCancel Click(object sender, EventArgs e}

{

// Прервать операцию

this.backgroundWorkerl.CancelAsync();

}

private void backgroundWorkerl_DoWork(

object sender, DoWorkEventArgs e)

{

// Здесь нельзя использовать прямую ссылку на backgroundWorkerl. // Правильно использовать параметр sender. BackgroundWorker bw = sender as BackgroundWorker;

// Получаем аргумент int arg — (int)e.Argument;

// Собственно операция

e.Result — Calculation(bw, arg);

// Если операция прервана, то выставляем флаг Cancel

if (bw.CancellationPending)

{

е.Cancel = true;

}

}

// Вызывается при завершении операции private void backgroundWorkerl_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e}

{

// Пользователь нажал кнопку Прервать

if (е.Cancelled}

{

MessageBox.Show("Операция была прервана"}; return; .

// Была ошибка во время выполнения операции

if (е.Error != null)

{

string msgError = String.Format("Ошибка: (0}"f e.Error.Message);

MessageBox.Show(msgError};

return;

}

// Операция завершилась нормально

string msg = String.Format{"результат — (0}", e.Result); MessageBox.Show(msg);

}

// Отображение прогресса выполнения private void backgroundWorkerl__ProgressChanged ( object sender, progressChangedEventArgs e)

{

progressBarl.Value = e.progresspercentage; progressBarl.Invalidate(}; ;

}

const int LEN =20; // Длительная операция

private int Calculation(BackgroundWorker bw, int arg)

{

int result = 0;

while (Ibw.Cancellationpending} {

result += 1; Thread.Sleep(1000);

double progress = result * 100 / LEN;

bw.ReportProgress(Convert.ToInt32{progress});

if (result >= LEN) I/ отсчитываем LEN секунд break;

}

return result;

Рис. 12.6. Ошибка при попытке доступа

Для потокобезопасного доступа к компонентам используются методы invokeRequired () и invoke о. Код отображения прогресса выполнения будет выглядеть следующим образом:

private int Calculation(BackgroundWorker bw, int arg)

double progress = result * 100 / LEN; // Вызываем метод для установки нового значения SetProgress(Convert.ToInt32(progress));

}

// Специальный делегат, используемый в методе Invoke delegate void SetProgressCallback(int progress);

// Метод для установки нового значения progress. Он же // используется как параметр для делегата

private void SetProgress(int progress) {

// Требуется вызов Invoke

if (progressBarl.InvokeRequired) {

// Создаем делегат и вызываем Invoke

SetProgressCallback d = new SetProgressCallback(SetProgress); this. Invoke {d, new objectU { progress});

else

// Можно устанавливать значение напрямую progressBarl.Value = progress; progressBarl.Invalidate{);

}

}

Теперь вызов будет потокобезопасным.

Еще один способ организации выполнения асинхронных операций (операций,

выполняемых В других потоках)—— использование класса AsyncOperationManager,

Описание работы этого класса не совсем укладывается в тему книги, поэтому я расскажу о нем достаточно кратко, точнее, просто приведу полезный пример его использования. Пусть, например, на форме имеется компонент listBox, использующийся для регистрации действий некоторых методов, выполняющихся в других потоках (журнал). При загрузке формы мы будем создавать шесть потоков: один поток, в котором будет выполняться метод LongOperationo, и пять отдельных потоков, представленных классом Someoperation. Каждый поток должен "отчитываться" о ходе работы, добавляя строки в компонент listBox основной формы. Добавление строк выполняется с помощью метода OutMessageO :

private void OutMessage(object item)

listBoxl.Selectedlndex = listBoxl.Items.Add(item);

}

Для решения этой задачи мы создадим специальный класс Calibackinvoker, метод invoke о которого будет запускать асинхронную операцию:

asyncOperation = AsyncOperationManager.CreateOperation{null};

public void Invoke{object userData) {

asyncOperation.Post(callback, userData};

}

Экземпляр этого класса мы будем передавать во все методы, требующие работы с журналом:

calibackinvoker = new Calibackinvoker(OutMessage); SomeOperation client = new SomeOperation(i + 1, calibackinvoker);

Сами методы нужны нам только для примера, поэтому они очень просты: все действия сводятся к некоторой задержке и выводу нескольких строк в журнал. Самое главное в них — это то, что при необходимости добавить строку в журнал вызывается метод invoke о нашего класса-обертки, а не прямое добавление строки в компонент listBoxi основной формы:

callbacklnvoker.Invoke{"SomeOperation: " + id);

Полный код описанного механизма приведен в листинге 12.11. Более детальное описание асинхронных операций и миогопоточности можно найти в соответствующей литературе.

Листинг 12.11 Использование компонента A->ynoO^>*Lat.bC»iMdnagar

public partial class Forml : Form {

private Callbacklnvoker callbacklnvoker;

public Forml(} {

InitializeComponent{);

}

private void Forml_Load{object sender, EventArgs e} {

// Создаем класс-обертку для OutMessage,

// внутри которого будет стартовать асинхронный вызов

callbacklnvoker = new Callbacklnvoker(OutMessage};

// Запустить выполнение метода LongOperation в отдельном потоке new Methodlnvoker{delegate () {

this.LongOperation(callbacklnvoker); }).Beginlnvoke{null, null);

// Создать клиентов, которые выполняют // свою работу в отдельных потоках

for {int i = 0; i < 5; i++) {

// Клиент уведомляет о ходе // работ через общий callbacklnvoker

SomeOperation client = new SomeOperation{i + 1, callbacklnvoker}; client.Run{} ;

// Метод, добавляющий строку в listBoxl private void OutMessage(object item)

listBoxl.Selectedlndex = listBoxl.Items.Add(item);

}

// Выполнение длительной операции

private void LongOperation(Callbacklnvoker callbacklnvoker) {

for (int i = 0; i < 5; i++) {

// Немного подождать

System.Threading.Thread.Sleep(new Random(}.Next(300, 600));

// Вызвать метод OutMessage callbacklnvoker.Invoke(

"LongOperation; " + DateTime.Now.ToString()};

}

callbacklnvoker.Invoke("Операция LongOperation завершена");

}

}

// Специальный класс, сохраняющий callback-метод // и запускающий асинхронную операцию

public class Callbacklnvoker {

private SendOrPostCallback callback; private AsyncOperation asyncOperation;

public Callbacklnvoker(SendOrPostCallback callback) {

// Ссылка на callback-метод this.callback = callback; // Асинхронная операция

asyncOperation = AsyncOperationManager.CreateOperation(null);

}

public void Invoke(object userData} {

asyncOperation.Post{callback, userData};

// Класс, выполняющий некую длительную операцию в методе Run

public class SomeOperation {

private int id;

private Callbacklnvoker callbackinvoker;

public SomeOperation(int id, Callbacklnvoker callbackinvoker) this.id = id;

this.callbacklnvoker = callbacklnvoker;

}

public void Run() •

{

// Запустить отдельный поток new Methodlnvoker(delegate()

// Немного подождать

Thread.Sleep(new Random(}.Next(100, 300)};

// Вызвать метод Forml.OutMessage

for {int i = 0; i < 3; i++) {

// Сообщение в основной поток

callbacklnvoker.Invoke("SomeOperation: " + id); // Немного подождать

Thread.Sleep(new Random(}.Next(10, 30));

}

}).Begininvoke(null, null);

}

}

12.6. Взаимодействие с Win32 API

Библиотека .NET Framework позиционируется как мультиплатформенная, поэтому в ней не содержится код, специфичный только для одной платформы. Соответственно, функции, существующие только в Windows, не входят в стандартную библиотеку. Кроме того, функции Win32 API не являются управляемыми и не могут быть использованы в .NET-коде напрямую. Для обращения к таким функциям необходимо подключать или, другими словами, импортировать их.

Импорт функций Win32 API производится с помощью атрибута diiimport, например: [Dlllmport("User32.dll") ]

private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);

Этот атрибут находится в пространстве имен System.Runtime. interopServices. В минимальном варианте достаточно просто имени библиотеки, из которой производится импорт. Но вообще говоря, атрибут Dlllmport имеет еще несколько полезных полей:

П поле charSet (тип charSet) позволяет задавать кодировку символов строк,

используемых функцией; П поле Entrypoint (тип string) может задавать имя импортируемой функции. По умолчанию имя импортируемой функции совпадает с именем описываемой функции;

П поле SetLastError (тип bool) указывает, что после вызова функции будет вызываться функция Win32 GetLastError. Код ошибки можно получить С помощью метода Marshal.GetLastWin32Error. Углубляться в тонкости импорта функций Win32 я не буду. Достаточно подробное описание можно найти в [3], а на сайтах custom.programming-in.net и pinvoke.net можно найти уже готовые обертки для функций Win32. Здесь я расскажу ровно столько, сколько потребуется нам для реализации примера. В качестве примера я покажу, как с помощью импортированных функций можно сделать свое окно подсказки, имеющее нестандартную форму. В .NET 2.0 такой компонент входит в список стандартных, но сейчас наша задача состоит не в изучении стандартной библиотеки. Нам просто надо разобраться как использовать возможности Win32 API.

Итак, мы имеем описание импортированных функций Win32. Первая сложность заключается в том, что в .NET нет описания констант, часто необходимых для работы с Win32, и нам придется сделать такие описания самостоятельно. Обычно константы просто берут из соответствующих заголовочных файлов С++. С простыми константами все проходит без проблем:

private const int TTS_ALWAYSTIP = OxOl; private const int TTS_NOPR?FIX = 0x02; private const int TTS_BALLOON – 0x40;

А вот с константами, имеющими большие значения, например 0x80000000, возникает сложность, заключающаяся в том, что компилятор С# не может допустить произвола преобразования беззнакового значения к знаковому, как это допускает компилятор С++. При попытке описать такую константу с типом mt мы получим ошибку "Cannot implicitly convert "type ‘uint’ to ‘int’" ("Невозможно неявно привести тип uint к типу int").

Прямое приведение типа тоже не помогает: "Constant value ‘2147483648’ cannot be converted to a ‘inf" ("Константа ‘2147483648’ не может быть преобразована к типу int").

Вариантов два — либо изменить описание импортированной функции, изменив в заголовке функции тип int на uint (что не всегда удобно), либо с помощью ключевого слова unchecked "уговорить" компилятор не обращать внимание на такое преобразование:

private const int WS_P0PUP = unchecked{(int)0x80000000};

Следующая проблема — описание структуры Tooiinfo, которая используется для описания окна всплывающей подсказки:

private struct Tooiinfo {

public int size;

public int flag;

public IntPtr parent;

public int id;

public Rectangle rect;

public int nullvalue;

[Marsha lAs(UnmanagedType.LpTStr))

public string text;

public int param;

}

Так как мы собираемся передавать эту структуру в методы Win32, то, во- первых, нужно "совместить" типы и длины полей структуры так, чтобы нужные байты оказались на одинаковых местах и для среды .NET, и для ядра Windows. Для простых типов, таких как int, это делается автоматически, а вот для строк, которые в С# работают совсем не так, как в С++, придется указать проецирование типа явным образом, с помощью атрибута MarshaiAs:

[MarshaiAs(UnmanagedType.LPTStr}] public string text;

Наиболее используемыми являются следующие значения этого атрибута (кроме самого типа могут указываться дополнительные параметры):

CJ Bool— четьгрехбайтовый тип Boolean. Соответствует Wtn32-THny bool.

true означает ненулевое значение, false — ноль; ? ByVaiArray— массив. Для этого типа должно быть указано значение поля Sizeconst, равное числу элементов в массиве. Дополнительно параметр ArraySubType может указывать тип элементов массива;

П ByVaiTStr — строки конечной длины в стиле Си (соответствуют описанию вида chars[5]). Для этого значения должны дополнительно указываться параметр кодировки structLayoutAttribute.CharSet и Параметр размера

SizeConst;

П Error — соответствует Win32-типу hresult;

Пц— соответствует типу bool языка Си. Значение true равно 1, значение false равно 0;

П 12,14,18 — соответственно 2-, 4- и 8-байтовые целые числа со знаком; П r4, R8 — 4- и 8-байтовые числа с плавающей точкой; ? in, U2, U4, us — целые беззнаковые числа;

П variantBool— аналог variant_bool. Значение true соответствует 1, значение false соответствует о. Например, в данном случае неуправляемый код не может определить размер массива данных по описанию, поэтому нужно указать его явно:

[MarshalAs{UnmanagedType.ByValArray, SizeConst=8)] public byte[] data4;

Во-вторых, проблема со структурами заключается в том, что политика работы в Win32 подразумевает частичное заполнение полей структуры. Чаще всего, для одних вызовов требуется заполнить одну часть полей, для других — другую. В С++ это считается вполне нормальным, а вот компилятор С# предупреждает, что часть полей не используется: "Field ‘CustomTooltip. TooltipsClassWin32.ToolInfo.id’ is never assigned to, and will always have its default value 0" ("Полю CustomTooltip.TooitipsC!assWin32.Too!lnfo.id никогда не присваивается значение и оно всегда будет иметь значение 0"). Отключить это предупреждение можно с помощью опции препроцессора: #pragma warning disable 64 9

Теперь "обернем" все вызовы Win32 в класс Tooitipsciasswin32, для того чтобы скрыть детали вызова неуправляемого кода от других классов (листинг 12.12). Теперь остается только сделать компонент, реализующий уже известный нам интерфейс lExtenderProviderService (см. листинг 12.1), с помощью которого мы добавим глобальное свойство BaiionText. В соответствии с этим нам потребуются методы GetBalloonText() и SetBalloonTextO, реализующие само свойство, и метод can?xtend(}, говорящий дизайнеру, что наше глобальное свойство применимо ко всем визуальным компонентам, кроме самой формы. Кроме того, несколько свойств потребуется для редактирования текста (Title), размера (MaximumWidth), значка (icon) и других свойств самой подсказки. Каждое свойство делает двойную работу— устанавливает значение внутреннего поля компонента и сразу же передает вызов в неуправляемый код через класс-обертку:

toolWindow = new TooltipsClassWin32(max};

public Color ForeColor

{

get

{

return fgcolor;

}

set

{

fgcolor = value;

toolWindow.SetForeColor(fgcolor);

}

}

Полный код компонента показан в листинге 12.13. Я думаю, что приведенной информации вполне достаточно, чтобы разобраться в этом коде. Еще раз подчеркну, что цель этого раздела состоит не в изучеиии конкретного класса, а в показе возможности взаимодействия с Win32. Надеюсь, поставленной цели я добился.

Листинг 1212 Класс-обертка для ШшЗг-функции управления окном подсказки

using System;

using System.Runtime.interopServices; using System.Drawing; using System.Windows.Forms;

nainespace CustomTooltip {

public class TooltipsClassWin32 {

private IntPtr toolwindow; private Toolinfo toollnfo;

// Конструктор

public TooltipsClassWin32(int max} {

// Создаем окно класса tooltips_class32 toolwindow = CreateWindowEx(

0, "tooltips_class32", string.Empty, WS_POPUP | TTS_BALLOON | TTS_NOPREFIX I

TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CWJJSEDEFAULT, IntPtr.Zero, 0, 0, 0); SetWindowPos(toolwindow, TOPMOST, 0, 0, 0, 0,

SWP_NOMOVE I SWP_NOSIZE I SWP_NQACTIVATE}; SendMessage(toolwindow, TTM__SETMAXTiPWIDTH, 0, new IntPtr(max});

// Специальная структура Toolinfo для управления // окном подсказки toolinfo = new Toolinfo();

toolinfo. flag = TTF_SUBCLASS I TTF__TRANSPARENT; toolinfo.size = Marshal.SizeOf(typeof(Toolinfo}};

}

// Добавляем текст подсказки

public void AddBalloonText(Control parent, string value) {

SetBalloonText(parent, value, false);

}

// Обновляем текст подсказки

public void UpdateBalloonText(Control parent, string value} {

SetBalloonText(parent, value, true};

}

// Удаляем текст подсказки

public void RemoveBalloonText(Control parent}

{

toolinfo.parent = parent.Handle;

IntPtr tempptr = Marshal.AllocHGlobal(toolinfo.size); Marshal.StructureToPtr(toolinfo, tempptr, false); SendMessage (toolwindow, TTM__DELTOOL, 0, tempptr); Marshal.FreeHGlobal(tempptr);

}

// Максимальная ширина

public, void SetMaxWidth(int max) {

SendMessage (toolwindow, TTM__SETMAXTIPWIDTH, 0, new IntPtr(max}};

// Активность окна

public void SetEnabled(bool enabled)

{

SendMessage (toolwindow, TTM__ACTIVATE,

Convert.ToInt32(enabled), new IntPtr(O));

}

// Заголовок и значок

public void SetTitle(string title, Balloonlcons icon) {

IntPtr tempptr = Marshal.StringToHGlobalUni(title); SendMessage(toolwindow, TTM_SETTITLE, (int)icon, tempptr); Marshal.FreeHGlobal(tempptr);

}

// Время показа окна подсказки public void SetAutoPop(int autopop)

{

SendMessage(toolwindow, TTM_SETDELAYTIME, TTDT_AUTOPOP, new IntPtr(autopop));

}

// Время ожидания до отображения окна подсказки

public void Setlnitial(int initial) {

SendMessage(toolwindow, TTM_SETDELAYTIME, TTDT_INITIAL, new IntPtr(initial));

}

// Фон окна

public void SetBackColor(Color backColor) {

SendMessage(toolwindow, TTM_SETTIPBKCOLOR,

ColorTranslator.ToWin32(backColor), new IntPtr(0));

}

II Цвет текста

public void SetForeColor(Color foreColor) {

SendMessage(toolwindow, TTM_SETTIPTEXTCOLOR,

ColorTranslator.ToWin32(foreColor), new IntPtr(0));

II Вызывается при изменении размеров public void Resize(object sender) (

toollnfo.parent = ((Control)sender).Handle;

IntPtr tempptr – Marshal.AllocHGlobal(toollnfo.size); Marshal.StructureToPtr{toollnfo, tempptr, false);

SendMessage(toolwindow, TTM_GETTOOLINFO, 0, tempptr);

toollnfo = (Toollnfo)Marshal.PtrToStructure{

tempptr, typeof(Toollnfo)); toollnfo.rect = {(Control)sender).ClientRectangle;

Marshal.StructureToPtr(toollnfo, tempptr, false);

SendMessage(toolwindow, TTM_SETTOOLINFO, 0, tempptr);

Marshal.FreeHGlobal(tempptr);

}

// Установка или обновления текста подсказки private void SetBalloonText(Control parent,

string value, bool update)

{

toollnfo .parent – parent .Handle toollnfo.rect = parent.ClientRectangle; toollnfo.text = value;

IntPtr tempptr – Marshal.AllocHGlobal(toollnfo.size); Marshal.StructureToPtr(toollnfo, tempptr, false);

if (update)

{

SendMessage(toolwindow, TTM_UPDATETIPTEXT, 0, tempptr);

}

else

{

SendMessage(toolwindow, TTM_ADDTOOL, 0, tempptr);

}

Marshal. FreeHGlobal (tsipptr) ;

// Деструктор ~TooltipsClassWin32()

{

DestroyWindow(toolwindow);

}

#region Win32-KOHCTaHTbi private const int TTS_ALWAYSTIP = 0x01; private const int TTS_NOPREFIX = 0x02; private const int TTS_BALLOON = 0x40;

private const int TTF_SUBCLASS = 0x0010; private const int TTF_TRANSPARENT ~ 0x0100;

private const int SWP_NOSIZE = 0x0001; private const int SWP_NOMOVE = 0x0002; private const int SWP_NOACTIVATE = 0x0010;

private const int TTDT_AUTOpOp = 2; private const int TTDT_INITIAL = 3;

private const int WS_P0PUP = unchecked<(int)0x80000000); private const int CW_USEDEFAULT – unchecked((int)0x80000000);

private IntPtr TOPMOST = new IntPtr(-1);

private const int WM_USER                        – 0x0400;

private const int TTM_ADDTOOL                    = WM_USER + 50;

private const int TTM_DELTOOL                    = WM_USER + 51;

private const int TTM_ACTIVATE                   – WM_USER + 1;

private const int TTM_SETMAXTIPWIDTH             = WM_USER + 24;

private const int TTM_SETTITLE                   = WM_USER + 33;

private const int TTM_SETDELAYTIME               – WM__USER + 3;

private const int TTM_QPDATETIPTEXT              = WM_USER + 57;

private const int TTM_SETTIPBKCOLOR        = WM_USER + 19; private const int TTM_ S ETTIPTEXT COLOR = WM_USER + 20;

private const int TTM_GETTOOLINFO                = WM_USER + 53;

private const int TTM_SETTOOLINFO          = WM_USER + 54; #endregion

#region Win32-функции [DllImportCuser32.dll") I

private static extern IntPtr CreateWindowEx( int exstyle, string classname, string windowtitle, int style, int x, int y,

int width, int height, IntPtr parent, int menu, int nullvalue, int nullptr);

[Dlllmport("user32.dll")]

private static extern int DestroyWindow(IntPtr hwnd); [Dlllmport("User32.dll")]

private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndlnsertAfter,

int X, int Y, int cx, int cy, int uFlags); [Dlllmport("User32.dll")]

private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam); #endreg ion

#regian Win32 структуры #pragma warning disable 64 9 private struct Toolinfo (

public int size;

public int flag;

public IntPtr parent;

public int id/

public Rectangle rect;

public int nullvalue;

[MarshalAs(UnmanagedType.LPTStr)]

public string text;

public int param;

}

#pragma warning restore 64 9 #endregion

}

public enum Balloonlcans : int (

None = 0, Info = 1, Warning = 2, Error = 3

Листинг 12 13. Класс feiilooi тсоггър, реализующим интсрфоис

lExtendor fri ovirtf»r

using System;

using System.Runtime.InteropServices;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Drawing;

using System.Collections;

using System.Windows.Forms;

using System.Windows.Forms.Design;

namespace CustomTooltip

{

// Атрибут ProvideProperty указывает, что свойство BalloonText II будет добавлено ко всем компонентам. Для этого используется II класс IExtenderProvider.

[Provideproperty("BalloonText"; typeof(Control))3 public class BalloonToolTip : Component, IExtenderProvider

{

private int max; private int autopop; private int initial; private bool enabled; private string title; private Hashtable toolTexts; private Balloonlcons icon; private Color bgcolor; private Color fgcolor;

II Win32-o6epTKa для управления II окном всплывающей подсказки private TooltipsClassWin32 toolWindow;

// Конструктор public BalloonTooITip()

{

II Инициализация свойств max = 200; autopop = 5000; initial = 500; title – string.Empty;

bgcolor — Color.FromKnownColor(KnownColor.Info);

fgcolor = Color.FramKnownColor(KnownColor.InfoText);

toolTexts = new HashtableO;

enabled = true;

icon = Balloonlcons.None;

// Создаем Win32~Knacc всплывающей подсказки toolWindow = new TooltipsClassWin32(max);

11 Деструктор -BalloonToolTip() (

Dispose(false);

}

11 Реализация метода CanExtend интерфейса IExtenderProvider. II Глобальное свойство может быть добавлено к любому П компоненту, кроме самого компонента BalloonToolTip и формы public bool CanExtend(object extendee) (

if (extendee is Control &&

!(extendee is BalloonToolTip) && !(extendee is Form))

{

return true;

}

return false;

}

II Метод Get свойства BalloonText [DefaultValue("")]

public string GetBalloonText{Control parent)

{

if (toolTexts.Contains(parent))

return toolTexts [parent] .ToStringO ; else

return null;

// Метод Set свойства BalloonText

public void SetBalloonText(Control parent, string value) (

if (value ==» null) (

value = string.Empty;

}

// Указание пустой строки

// означает удаление всплывающей подсказки if (value = string.Empty) (

toolTexts.Remove(parent);

toolwindow.RemoveBalloonText(parent);

parent.Resize -= new EventHandler(Control_Resize);

}

else

{

// Если всплывающее окно уже создано, обновляем значение if (toolTexts.Contains(parent)) (

toolTexts[parent] — value;

toolwindow.UpdateBalloonText(parent, value);

}

else

{

// Если окно еще не создано, то создаем его toolTexts.Add(parent, value); toolwindow.AddBalloonText(parent, value);

}

parent.Resize += new EventHandler(Control_Resize);

}

)

[Description("Значок окна подсказки")] [DefaultValue(Balloonlcons.None)] public Balloonlcons Icon

{

get

{

return icon;

set

1

icon = value;

toolwindow.SetTitle{title, icon);

}

}

[0езсг1р^оп("Максимальная ширина") ] [DefaultValue(200)]

public int MaximumWidth

{

get

{

return max;

}

set

{

max = value;

toolwindow.SetMaxWidth(max);

}

)

E Description("Активность")] [DefaultValue(true)]

public bool Enabled {

get

{

return enabled;

}

set

{

enabled = value;

toolwindow.SetEnabled(enabled);

}

)

[Description("Строка подсказки")} public string Title

{

get {

return title;

set {

title = value;

toolWindow.SetTitle{title, icon);

}

}

[Description{"Время показа окна (в сек.)")] [DefaultValue(5)}

public int AutoPop {

get {

return autopop / 1000;

}

set

{

. autopop = value * 1000; toolWindow.SetAutoPop{autopop);

}

}

[Description("Время ожидания до отображения окна подсказки (мс)")] [DefaultValue(500)]

public int Initial

{

get {

return initial;

}

set

{

initial = value;

toolWindow.Setlnitial(initial);

}

}

[Description{"Фон окна")]

public Color BackColor {

get

{

return bgcolor;

set {

bgcolor — value;

toolWindow.SetBackColor(bgcolor);

}

)

[Description("Цвет текста")]

public Color ForeColor {

get

{

return fgcolor;

}

set

{

fgcolor = value;

toolWindow.SetForeColor(fgcolor) ;

}

}

// Освобождение ресурсов

protected override void Dispose(bool disposing)

{

if (disposing) {

toolTexts.Clear() ; toolTexts = null;

}

toolWindow = null; base.Dispose(disposing);

}

// Изменение размеров компонента

private void Control_Resize(object sender, EventArgs e) {

toolWindow.Resize(sender);

Литература:

Агуров П. В. C#. Разработка компонентов в MS Visual Studio 2005/2008. – СПб.: БХВ-Петербург, 2008. — 480 е.: ил.

По теме:

  • Комментарии