Internet Explorer може да изпълнява Windows Froms контроли, вградени в тялото на HTML страници. Технологията е подобна на Java аплетите и Macromedia Flash – вгражда се изпълним код, който се изпълнява в клиентския уеб браузър. От JavaScript могат да се достъпват свойствата на Windows Forms контролите. Необходими са Internet Explorer 5.5, или по-нова версия, и инсталиран .NET Framework.
Настройките за сигурност не позволяват достъп до файловата система и други опасни действия. Сигурността може да се задава и ръчно. Ако има нужда от запазване на някакви данни на машината на потребителя, може да се използва Isolated Storage.
Хостинг на контроли в Internet Explorer – пример
Настоящият пример илюстрира как можем да реализираме хостинг на Windows Forms контроли в Internet Explorer чрез вграждането им в HTML страница и как можем да достъпваме свойствата им от JavaScript.
Да разгледаме примерна HTML страница, в която е вградена Windows Forms контролата "часовник" от предходния пример:
index.html
Clock Control in IE
Hour:
Minute:
Как работи примерът?
Нека разгледаме по-подробно отделните части на HTML страницата. Чрез HTML тага вмъкваме в страницата нашата контрола. Това е часовникът, който създадохме в предишния пример. Атрибутът id, който има стойност clockContol, указва идентификатор, чрез който ще можем да достъпваме обекта в HTML страницата, а атрибутите width и height указват с каква ширина и височина да се изобрази той. Атрибутът classid определя класа на вмъквания обект. В случая това е нашата ClockControl контрола. Забележете, че указваме асемблито, пространството и името на класа в стойността на този атрибут. В случая сме поставили асемблито Demo-18-CustomControl-Clock.exe в директорията, в която се намира и HTML страницата. Чрез таговете задаваме стойности за свойствата на изобразяваната контрола.
Под контролата сме поставили две текстови полета и един бутон. Текстовите полета служат за въвеждане на час и минути, които да показва часовникът. Бутонът служи за промяна на стрелките на часовника. При натискането му се извиква JavaScript функцията ChangeText(), дефинирана в началото на страницата, която променя свойствата на контролата. Достъпът до текстовите полета и до контролата се извършва посредством техните идентификатори, зададени чрез атрибута id.
Примерът в действие
За да видим резултата от нашата работа, трябва да използваме Internet Explorer 5.5 или по-нов. Не е известен друг уеб браузър, който поддържа Windows Forms контроли.
Ако отворим директно index.html в Internet Explorer, контролата ClockControl няма да се зареди заради политиката за сигурност, която не позволява локално разположени HTML документи да изпълняват Windows Forms контроли. Необходимо е страницата да бъде публикувана на някакъв уеб сървър, например IIS.
Нека файловете ни се намират в папката Demo-19-Custom-Controls-in-IE. Публикуването на папката в Internet Information Services (IIS) се извършва по следния начин:
От свойствата на папката Demo-19-Custom-Controls-in-IE, достъпни от диалоговата кутия на Windows Explorer, избираме таба "Web Sharing". В него избираме "Share this folder".
Публикуваме папката Internet Information Services, като позволим четене на файловете и листинг на директориите.
Сега можем да отворим с Internet Explorer URL адреса на примера от публикуваната в IIS директория:
Ако въведем час и минута и натиснем бутона, стрелките ще променят местоположението си.
Нишки и Windows Forms
Продължителните операции в Windows Forms приложенията трябва да се изпълняват в отделна нишка. В противен случай се получава "заспиване" на потребителския интерфейс. Как можем да използваме нишки, ще разгледаме подробно в темата "Многонишково програмиране и синхронизация", но засега можем да считаме, че нишките позволяват паралелно изпълнение на програмен код в нашите приложения.
Да вземем за пример операцията "изтегляне на файл от Интернет". Тя може да отнеме от няколко секунди до няколко часа и е недопустимо приложението да блокира, докато изтеглянето на файла не приключи. В такъв случай трябва да изпълним задачата в друга нишка (thread) и от време на време да показваме на потребителя индикация за напредъка, например чрез контролата ProgressBar. Има обаче един проблем, свързан с достъпа до потребителския интерфейс при работа с нишки.
Обновяването на потребителския интерфейс на дадена контрола трябва да става само от нишката, в която работи контролата. От друга нишка безопасно могат да се извикват само методите Invoke(), BeginInvoke(), EndInvoke() и CreateGraphics().
Никога не обновявайте Windows Forms контроли от нишка, която не ги притежава!
За изпълнение на методи от нишката, която притежава дадена контрола, използваме метода Invoke(…) на класа Control. Ето пример:
delegate void StringParamDelegate(string aValue);
class Form1 : System.Windows.Forms.Form
{
private void UpdateUI(string aValue)
{
// Update UI here …
// This code is called from the Form1's thread
}
void AsynchronousOperation()
{
// This runs in separate thread. Invoke UI update
this.Invoke(new StringParamDelegate(UpdateUI),
new object[]{"някакъв параметър"});
}
}
По този начин нишката, която извършва времеотнемащата работа, работи паралелно на нишката, която управлява потребителския интерфейс, но той се обновява само от неговата нишка-собственик. Ако обновяваме потребителския интерфейс от нишката, която извършва времеотнемащата операция, а не от главната нишка на приложението, се получават много странни ефекти – от "зависване" на приложението до неочаквани изключения и системни грешки. Не го правете!
Използване на нишки в Windows Forms приложения – пример
С настоящия пример ще илюстрираме използването на нишки (threads) в Windows Forms приложения за изпълнение на времеотнемащи задачи. Ще покажем правилния начин, по който една нишка, която се изпълнява паралелно с главната нишка на Windows Forms приложението, може да обновява неговия потребителски интерфейс.
Приложението, което ще създадем, ще търси прости числа (което е времеотнемаща операция) и ще ги показва на потребителя. Търсенето ще се извършва в отделна, паралелно изпълняваща се нишка, за да не "заспива" потребителският интерфейс.
Ето стъпките за изграждане на нашето приложение:
Стартираме VS.NET и създаваме нов Windows Forms проект.
Задаваме на главната форма име MainForm и заглавие "Asynchronos UI Update Demo". Променяме и името на файла от Form1.cs на MainForm.cs.
Добавяме във формата два бутона с имена ButtonStart и ButtonStop и един TextBox с име TextBoxLastPrimeNumber. На свойствата Text на бутоните задаваме съответно стойности Start и Stop. Задаваме стойност false на свойството Enabled на бутона ButtonStop.
Добавяме променлива за нишката, която търси прости числа:
private Thread mPrimeNumbersFinderThread = null;
Декларираме делегат, който ще използваме при извикването на метода Invoke(…), когато обновяваме потребителския интерфейс:
delegate void LongParameterDelegate(long aValue);
Дефинираме клас PrimeNumberFinder, чрез който ще търсим прости числа в интервала [0; 1 000 000 000]:
new LongParameterDelegate(mMainForm.ShowPrimeNumber),
new object[]{number}
);
}
}
}
private bool IsPrime(long aNumber)
{
// Primarity testing. Very ineffective.
// Don't do it in a real case!!!
for (long i=2; i
{
// Just waste some CPU time
int sum = 0;
for (int w=0; w<100000; w++)
{
sum += w;
}
if (aNumber % i == 0)
{
return false;
}
}
return true;
}
}
Понеже търсенето на прости числа ще се извършва в отделна нишка, в класа сме дефинирали променлива mMainForm, чрез която ще се обръщаме към главната форма, за да обновяваме потребителския интерфейс. Тази променлива се инициализира в конструктора на класа.
Методът IsPrime(…) проверява дали подаденото като параметър число е просто. Тази проверка нарочно се прави по изключително времеотнемащ, неефективен и натоварващ процесора начин, за да се симулира забавяне.
Методът FindPrimeNumbers() проверява последователно дали е просто всяко от числата в интервала от 0 до 1000000000. Ако числото е просто, през главната нишка на приложението се извиква методът ShowPrimeNumber(…), като му се подава като параметър намереното просто число. Този метод показва числото в потребителския интерфейс. Извикването се извършва чрез метода Invoke(…) на формата, който има грижата да изпълни подадения му делегат през нишката, в която работи формата.
Нишката, която търси прости числа, няма право да променя директно потребителския интерфейс на приложението, защото той работи в друга нишка. Ако две нишки работят с потребителския интерфейс едновременно, могат да възникнат непредвидими проблеми – блокиране на приложението, странни изключения или странни визуални ефекти.
Дефинираме в главната форма метода ShowPrimeNumber(…), който показва подаденото му като параметър число в текстовото поле TextBoxLastPrimeNumber:
internal void ShowPrimeNumber(long aNumber)
{
TextBoxLastPrimeNumber.Text = aNumber.ToString();
}
Добавяме обработчик на събитието Click на бутона ButtonStart. В него деактивираме Start бутона, активираме бутона Stop и стартираме отделна нишка, в която започваме да търсим прости числа:
PrimeNumbersFinder finder = new PrimeNumbersFinder(this);
mPrimeNumbersFinderThread =
new Thread(new ThreadStart(finder.FindPrimeNumbers));
mPrimeNumbersFinderThread.Start();
}
Добавяме обработчик на събитието Click на бутона ButtonStop. В него активираме Start бутона, деактивираме бутона Stop и прекратяваме изпълнението на стартираната нишка:
Добавяме обработчик на събитието Closing на главната форма. В него прекратяваме изпълнението на нишката, търсеща прости числа (в случай че е била стартирана):
private void MainForm_Closing(object sender,
System.ComponentModel.CancelEventArgs e)
{
if (mPrimeNumbersFinderThread != null)
{
mPrimeNumbersFinderThread.Abort();
}
}
Приложението е готово и можем да го стартираме и тестваме.
Въпреки че се извършва тежко изчисление и процесорът е натоварен на 100%, потребителският интерфейс не "замръзва". Ако все пак в даден момент се получи замръзване за кратко време, най-вероятно причината за това e включването на системата за почистване на паметта (Garbage Collector).