В статті – відповіді на запитання не для форумів.
Той, хто почав читати цю статтю, не є висококласним, професійним програмістом, бо ніякий пошуковець не знайде в статті слова, потрібні програмістам високого класу. Довелось і собі написати статтю, з часом це буде кілька статей, для програмістів, які ще недосконало володіють прийомами програмування на C# (Сі-шарп). Ці програмісти задають свої запитання на форумах. Що ж вони чують у відповідь ?
Візьмемо наугад одне з запитань, потрібних початківцю. Програміст-початківець, як може, формулює ось таку проблему:
Після запуску програми одне з віконець ComboBox (комбобокс), або TextBox, чомусь завжди є “відкритим”, і машинальний рух коліщатком миші змінює вибраний параметр цього віконця. Можна, звісно, пояснити всім користувачам програми, щоб після запуску програми натискали мишкою на якийсь інший об’єкт, або робили ще якісь додаткові дії, але це незручно. Як зробити, щоб після запуску програми всі віконця ComboBox чи TextBox, чи ще якісь, були пасивними ? Щоб не залежали від коліщатка мишки ?
Що чує програміст у відповідь ?
На англомовних форумах:
Place a box outside the visible portion of the screen, and Handle mouseWheel event traditional +=.. (можете не перекладати, все одно початківцю нічого не зрозуміло).
На російськомовних форумах:
Нужно добавить полное имя, включая название модуля, в котором определяется класс, а не просто сделать рекурсию, когда класс наследуется сам от себя.
Надзвичайно корисні і потрібні поради ! Такі зрозумілі і прості, як теорія відносності. Доведеться відповідати на такі запитання в своїй статті, але щоб відповіді були “не для продвинутих”, а для нормальних людей, які навчаються програмувати на C#.
Стаття лише починається, вона буде регулярно доповнюватись, на теперішній момент в статті розглянуто наступні запитання:
Запитання 1. Чому після запуску програми, написаної на C# яким-небудь звичним Visual Studio, одне з віконець, наприклад, Combobox, вже є відкритим, і реагує на коліщатко миші ? Чому після користування аналогічним іншим віконцем воно залишається “відкритим” і так само реагує на мишу ? Як позбавитись цієї особливості ?
Запитання 2. Хочу, щоб деякі змінні чи константи, оголошені в тілі одної форми (наприклад, Form1), були доступні з інших форм (наприклад, Form2), а може, навпаки, щось із форми Form2 було доступним з форми Form1.
Запитання 3. Хочеться, щоб якась із форм програми не перекривалась іншими формами, завжди була “спереду”. І розміщувалась там, де я хочу.
Запитання 4. Які ще є можливості для програмного керування розміром форми та розміщенням форми ?
Запитання 5. Графіка – для людей, а не для “продвинутих”. Це можливо ?
Власне, з першого запитання я починаю.
Запитання 1. Чому після запуску програми, написаної на C# яким-небудь звичним Visual Studio, одне з віконець, наприклад, Combobox, вже є відкритим, і реагує на коліщатко миші ? Чому після користування аналогічним іншим віконцем воно залишається “відкритим” і так само реагує на мишу ? Як позбавитись цієї особливості ?
Відповідь: Так вирішено професіоналами, що при запуску зробленої вами програми один з об’єктів на “формі” (загальному вікні вашої працюючої програми) завжди знаходиться “в фокусі”, і залежить від дій маніпулятора, в даному разі від “миші”. Щоб цей “фокус” не заважав, його треба перекинути на інший об’єкт програми. Наприклад, на якийсь надпис на “формі”. Як це зробити ?
Якщо в самому тілі програми “Form1” (назва взята наугад) помістити переназначення фокуса на інший об’єкт, наприклад, на якусь мітку, то це не допомагає. Ось дивіться:
public Form1()
{
InitializeComponent();
// ще якісь дії, потрібні в програмі, і нарешті в кінці
label1.Focus(); // непогана задумка, кидаємо фокус на мітку 1, але не працює
}
Можна зробити просту програмну хитрість. Вставимо в програму таймер, навіть якщо він нам не потрібен. Якщо не знаєте, який зробити період таймера, наугад поставте в параметрах 100 мс. До таймера додамо змінну величину-ідентифікатор стану фокуса, наугад назвемо її knop. Не зробіть машинальний ляп ! Змінна повинна бути за межами таймера.
int knop=0; // звичайна змінна, можна цілу, можна байтну, можна ще якусь
private void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if(knop==0){label1.Focus();knop=1;}; // якщо knop=0, то перекинути фокус на мітку 1
// якщо треба таймер ще для чогось, можна використати
}
А в наших віконцях ComboBox, TextBox чи подібних, додаємо такий трюк:
private void ComboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
// щось робимо в цьому віконці, що нам треба, а в кінці:
knop=0;
}
І це все ! Кожне з цих віконець вже не буде прив’язане до миші після того, як віконцем користувались при роботі програми. Ні одне з цих віконець не буде прив’язане до коліщатка миші після запуску програми. Таймер регулярно закидує фокус на вибрану нами мітку. Дрібна заважаюча проблема вирішена.
Запитання 2. Хочу, щоб деякі змінні чи константи, оголошені в тілі одної форми (наприклад, Form1), були доступні з інших форм (наприклад, Form2), а може, навпаки, щось із форми Form2 було доступним з форми Form1.
Змінна чи константа може бути доступна не лише зі своєї форми, а з усіх форм даної задачі.
До цього ми додаємо public, наприклад:
замість int Zminna; буде public int Zminna;
Вже краще, але компілятор бажає, щоб загальнодоступні елементи були статичними:
static public int Zminna;
Це вже веселіше, тепер, наприклад, із головної форми Form1 ця змінна доступна звичайним звертанням:
наприклад, Zminna = 5;
а з іншої форми можна звертатись: Form1.Zminna = 5;
Треба пам’ятати, що компілятор, який дещо робить замість нас, вказав, що сама форма Form1 є загальнодоступною, для цього компілятор, коли ми створювали форму, своєчасно написав:
public partial class Form1:Form
а значить, елементи форми Form1 МОЖУТЬ БУТИ загальнодоступними, якщо ми потурбуємось, щоб додати до цих елементів “static public“.
Чи можна елементам, які знаходять всередині підпрограми (“метода”, по-сучасному), присвоювати признаки “static” public” ? Ні, компілятор не дозволить, бо кожен раз, коли йде звертання до “метода”, в пам’яті комп’ютера виділяється тимчасове місце для для елементів, оголошених всередині “метода”. Які наслідки такої тимчасовості ? Повна тимчасовість, зараз поясню.
Звертаєтесь з головної програми до метода “Testovyi()” і всередині метода перевіряєте, чи справді все так тимчасово. Ось наприклад:
void Testovyi(){
int zmi;
if(zmi==5)label1.Text=”воно = 5″; else {label1.Text = “воно не = 5”;zmi=5;};
}
Звернемось до метода з головної програми один раз:
Testovyi();
на мітці “label1” з’явився надпис “воно не = 5“, а що буде, коли ми звернемось до метода два рази ?
Testovyi();
Testovyi();
Нічого не вийде, вперто бачимо надпис “воно не = 5“, хоча, начебто, надпис після другого звертання мав би змінитись на “воно = 5“.
Це підтвердження того, що всі елементи, оголошені всередині метода, є тимчасовими.
Можна, при бажанні, додати окремий, загальнодоступний клас, і в тілі того класу розмістити ті елементи програми, які мають бути доступними з усіх точок, усіх форм нашої програми.
Ось компілятор, коли ми створювали першу форму, створив для неї клас:
public partial class Form1:Form {
// далі ми накидали потрібні нам елементи для цієї форми, наприклад, змінну fff
float fff;
// далі – наша головна форма
public Form1()
{
InitializeComponent();
// а також всі інші дії, написані нами, які виконуться
// ОДИН РАЗ при запуску форми в дію
}
} // а ось закінчення оголошеного компілятором класу
//
// і тут ми можемо створити ще один клас, і оголосити його
// загальнодоступним з усієї задачі
//
static class VSIM // ми захотіли, і назвали клас VSIM
{
//
// сюди можна ставити загальні змінні чи константи
// щоб вони були загальнодоступними
//
public static FileStream fil1; // можна ставити елементи для роботи з файлами
public static byte TakaZminna; // можна байтні змінні,
public static int TcilaZminna=0;// можна цілі змінніі,
public static const float Plav = 1.25;// словом, що хочемо, те ставимо
} // не забули закрити клас
А тепер з будь-якої точки програми звертаємось:
VSIM.TakaZminna=25; // і бачимо, що ця змінна всюди доступна
Запитання 3. Хочеться, щоб якась із форм програми не перекривалась іншими формами, завжди була “спереду”. І розміщувалась там, де я хочу.
Наприклад, це одне з віконець в вашій програмі з якимось важливим параметром, чи надписом.
Пробуємо це зробити. Ось примітивна тестова програма.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1:Form
{
Form2 f2=new Form2();
public Form1() // наша “початкова” форма
{
InitializeComponent();
f2.Show(); // просто відкриємо форму 2, більше ніяких дій
}
//
// вставимо в програму таймер, дуже корисна річ, хоча начебно не потрібна
//
private void timer1_Tick(object sender,EventArgs e)
{ } // ось і таймер, він все одно поки що нічого не робить
}
}
Всі ці початкові рядки “using …….” я показав в статті один раз, вони – за замовчуванням.
Ще один таймер поставив в формі 2, вона також нічого не робить. Запускаємо програму, і що ми бачимо на екрані ?
Ні, це жахливо. По-перше, Form2 розмістилась, де сама хоче, а не там, де хочемо ми, по-друге, натиснули на Form1 – і ця форма спереду, натиснули на Form2 – тепер вона спереду.
Будемо виправляти. В тексті для Form2 додаємо команду, щоб форма при роботі була завжди спереду, і не закривалась іншими формами. Ось змінений текст:
public Form2()
{
InitializeComponent();
this.TopMost=true; // щоб завжди була зверху, доки не вимкнуть
}
Запускаємо програму. Це зовсім інше діло ! Хто тепер попробує закрити Form2 ? Погано лише, що вона не там, де нам хочеться. Коректуємо.
public Form2()
{
InitializeComponent();
this.TopMost=true; // щоб завжди була зверху, доки не вимкнуть
this.Location = new Point(Form1.Location.X, Form1.Location.Y);
}
Ага, якраз ! Помилка. Компілятор попереджує, що Form1 ми не робили статичною, а значить, доступ до параметрів Form1 не завжди можливий. Можна оголосити public static Form1, але полізуть інші незручності, кількість помилок збільшиться. Невже ми не викрутимось з цієї ситуації ?
Додамо в Form1 дві загальнодоступні змінні, для зручності ми назвали їх Koorx, Koory. В ці змінні наша Form1 запише координати своєї лівої верхньої точки.
public static int Koorx, Koory;
public Form1()
{
InitializeComponent();
Koorx=this.Location.X;Koory=this.Location.Y;
f2.Show();
}
А форма Form2 візьме ці координати.
public Form2()
{
InitializeComponent();
this.TopMost=true; // щоб завжди була зверху, доки не вимкнуть
this.Location = new Point(Form1.Koorx,Form1.Koory); // де головна форма, там і я
}
Дивимось, що тепер. Не працює ! Ми все зробили правильно, а от не працює.
Це не перша єхидна невідповідність, з якою ми будемо зустрічатись, програмуючи в Windows із використанням Visual Studio, і пояснення дуже просте.
Форма Form1 почала розгортатись і запустила форму Form2. Також почала розгортатись Form2 і використала змінні Koorx і Koory, в які ще не занесені правильні значення. Без таймера нам вже не обійтись. Тепер присвоєння значення змінним Koorx, Koory перенесемо в тіло таймера для Form1.
private void timer1_Tick(object sender,EventArgs e)
{
Koorx=this.Location.X;Koory=this.Location.Y;
}
а використання цих змінних перенесемо в тіла іншого таймера, який обслуговує форму Form2. Ми ж пам’ятаємо, для кожної форми ми зробили свій таймер, а таймер – предмет корисний.
private void timer1_Tick(object sender,EventArgs e)
{
this.Location = new Point(Form1.Koorx,Form1.Koory);
}
Запустимо програму.
Чудово ! Ми хотіли, щоб лівий кут верхній Form2 навіщось співпадав з лівим верхнім кутом Form1, і ми цього добились. Навіть якщо ми після запуску програми будемо мишкою пересувати форму Form1 по екрану, форма Form2 буде негайно пересуватись на нове місце.
Значить, питання з відображенням форм вирішене.
Запитання 4. Які ще є можливості для програмного керування розміром форми та розміщенням форми ?
Для відповіді згадаємо, які значки ми традиційно бачимо в правому верхньому куті форми. Ми бачимо приблизно таке:
Дуже знайомий вигляд.
Якщо ми напишемо в програмі: this.MinimizeBox = false; то користувач не зможе, натиснувши мишкою на значок A, сховати програму в “трей”. За замовчуванням вважається, що this.MinimizeBox = true; а те, що за замовчуванням, можна не писати.
Вибачте, налетіли на слово із жаргону програмістів. “Трей” – це звичайне англійське слово, перекладається як “лоток” чи “піднос”. Це нижня частина екрану вашого дисплея, там традиційно знаходяться значки, які показують, які програми зараз працюють, яка зараз мова на клавіатурі, скільки годин, ну, ще багато різних значків можна туди вставити.
Якщо напишемо в програмі this.MaximizeBox = false; то користувач не зможе натиснути на значок B, щоб розгорнути програму (форму) на весь екран.
Якщо навпаки, написати this.WindowState = FormWindowState.Maximized; то програма і сама розгорнеться на весь екран, не чекаючи, щоб користувач її розгорнув. Відповідно, надпис в програмі this.WindowState = FormWindowState.Minimized; негайно сховає працюючу програму в трей.
А як дізнатись поточні розміри форми ? Допустимо, у нас є дві змінні, silinaformy (тут буде ширина форми) і visotaformy (висота форми). Ось так дізнаємось про розміри:
silinaformy=this.Width;
visotaformy=this.Height;
Добре, а як в програмі дізнатись, які розміри вікна програми доступні саме на цьому дисплеї ? Ось так це можна хробити:
silinaformy=Screen.GetWorkingArea(this).Width;
visotaformy=Screen.GetWorkingArea(this).Height;
Можна програмно змінювати розміри форми:
this.Size = new Size(silinaformy,visotaformy);
А як бути із значком C (зупинка програми) ? Невже цей хрестик можна (при великій необхідності) прибрати ?
Так, і дуже просто: this.ControlBox = false; (всі три значки зникають, A, B і C )
А так, щоб лише хрестик прибрати ? Хрестик можна зробити неактивним, але тоді зняти задачу з виконання можна лише диспетчером задач. Доведеться звернутись до “подій” в властивостях форми.
Потрібну “подію” (тут нам потрібна подія “FormClosing“, закривання програми) в тексті програми описуєте одним рядком:
private void Podiya(object sender, FormClosingEventArgs e)
{
e.Cancel = true;
}
Тепер вашу задачу після запуску примусово зупинити ДУЖЕ ВАЖКО.
А як зробити цей хрестик неактивним, але щоб з трея можна було зупинити задачу ? Звісно, відповідь є. Доведеться додати до програми невеличку підпрограмку, яка справді зробить хрестик неактивним, не чіпаючи інші значки. Ця підпрограмка змінює необхідний біт у властивостях задачі (форми).
protected override CreateParams CreateParams
{
get
{
const int CS_NOCLOSE = 0x200;
CreateParams cp = base.CreateParams;
cp.ClassStyle = cp.ClassStyle | CS_NOCLOSE;
return cp;
}
}
Зовсім трошки про цю дивну підпрограму. Коли ваша задача при запуску захоче опитати, який стиль вікна Form1 оформлювати, блок запиту властивостей get додасть до властивостей класу потрібний біт у потрібному місці, розробники Visual Studio знають, де треба додати, і це зробить “хрестик” неактивним.
До вказаної підпрограми не треба звертатись з головного тіла програми, вона сама виконується один раз при запуску програми.
А як взагалі сховати цей хрестик на формі, щоб його не було ?
Не знаю. Не знайшов.
Запитання 5. Графіка – для людей, а не для “продвинутих”. Це можливо ?
Так, це можливо. Щоб програма могла легко малювати графічні елементи, не згинаючи програміста вимогами “не визначено”, “не допустимо” та іншими “не”, треба пам’ятати що програма малює НЕ НА ЕКРАНІ ДИСПЛЕЯ.
Програма малює на формі вашої задачі (повноекранний режим – це вже інше), а тому достатньо один раз вказати поверхню для малювання, і далі “само піде”.
Отже, наша тестова задача традиційно називається Form1. На цій формі і будемо малювати.
public partial class Form1 : Form
{
Graphics Forma; // оголошуємо графічний елемент, для зручності назвали Forma
//
// Нам потрібні різні пера для малювання
Pen Pero = new Pen(Color.FromArgb(255, 0, 0, 0), 1);
// тут інтенсивність = 255 (максимальна інтенсивність)
// 0,0,0 – це Червоний, Зелений, Синій кольори, тобто
// в даному випадку ніякого кольору, тобто чорний (відсутність кольорів).
// Значення кольорів – від 0 до 255.
// 1 – значить, товщина лінії – один піксель
//
// введемо ще одне перо
Pen Pero2Cont = new Pen(SystemColors.Control, 2);
// SystemColors.Control – використали готовий колір, в даному випадку
// це базовий фоновий колір нашої форми,
// а товщину лінії вибрали 2 пікселі.
//
// Готових кольорів у нас в Visual studio багато,
// створимо ще одне перо
Pen Pero3Sin = new Pen(Color.LightBlue,5); // світло-синій колір
// а товщина лінії = 5 пікселів
//
// Є ще група кольорів System.Drawing, візьмемо зелений
Pen PeroZel = new System.Drawing.Pen(System.Drawing.Color.Green);
//
// Пера вже маємо, тепер треба заготовити пензлі
Brush PenzelGre = new SolidBrush(Color.LightGray); // світло-сірий
Brush PenzelRizn = new SolidBrush(Color.FromArgb(255, 0, 0, 0));
// заготовили собі змінний пензель, назвали PenzelRizn,
// спочатку, як бачите, він чорний (0,0,0).
// Ми його будемо змінювати в програмі для
// створення будь-якого кольору.
//
// так само можна для пензля використовувати готові кольори
Brush PenzelSvitloRed = new SolidBrush(Color.Red); // світло-червоний
//
// будемо графікою малювати ще й текст, тому запаслись шрифтом
System.Drawing.Font RisFont = new System.Drawing.Font(“Microsoft Sans Serif”, 9);
// поки що досить
public Form1()
{
InitializeComponent();
Forma = this.CreateGraphics(); // все малюємо на цій формі
}
}
Отже, на початку програми, після InitializeComponent(); ми оголосили, що поверхнею для малювання буде наша форма Form1. Тепер, хоч з тіла головної програми, хоч з підпрограм (вибачте, методів) можемо малювати, і поверхня для малювання нам буде доступна.
Пора малювати, намалюємо лінію.
Forma.DrawLine(Pero, 10, 20, 30, 40);
Пояснення: малюємо на нашій формі Form1, тому вказали графічну поверхню “Forma“, для малювання взяли перо Pero, початкові координати лінії(X,Y) = 10, 20, а кінцеві координати 30,40.
Будь-які координати – не від верхнього лівого кути дисплея, а від верхнього лівого кута нашої форми.
Тепер малюємо прямокутник.
Forma.DrawRectangle(Pero3Sin, 10, 20, 30, 40);
Що вибрали, те маємо. Прямокутник буде намальовано синьою лінією, товщиною 5 пікселів (бо ми заготовили таке перо), координати лівого кута прямокутника 10, 20, ширина прямокутника 30, висота 40.
Не всюди в програмі нам потрібно буде вказувати параметри вибраними числами, іноді треба використати якісь змінні. Замалюємо прямокутник кольором.
Яким кольором ? Я звідки знаю ? Наша програма вирахувала собі якийсь колір, виявилось, що змінним величинам Kchervony, Kzeleny, Ksyniy програма присвоїла якісь значення, від 0 до 255.
Під вирахуваний колір створюємо собі пензель
PenzelRizn = new SolidBrush(Color.FromArgb(Kchervony, Kzeleny, Ksyniy);
Це той самий пензель, який ми оголосили на початку програми, просто ми його змінили перед малюванням. Тепер замалюємо прямокутник вирахуваним кольором.
Forma.FillRectangle(PenzelRizn, X1, Y1, Dowzina1, Visota1);
Я ж казав ! Не обов’язково вказувати якісь конкретні числа, можна використовувати змінні ідентифікатори, потрібні значення ми можемо розрахувати в програмі.
Може, пані хочуть намалювати еліпс ?
Forma.DrawEllipse(Pero, XElips, YElips, SurunaElips, VisotaElips);
Не знаю навіть, чи пояснювати. XElips, YElips – це координати лівого верхнього кута того прямокутника, в який буде вписано еліпс, а SurunaElips, VisotaElips – це ширина і висота того самого умовного прямокутника, в який буде вписано еліпс.
Еліпс можна замалювати будь-яким кольором:
Forma.FillEllipse(PenzelRizn, XElips, YElips, SurunaElips, VisotaElips);
Все таке саме, але використовуємо не перо, а пензель.
Ми можемо також “малювати текст”, при цьому у нас більше можливостей для “графічних викрутасів”, ніж при використанні, наприклад, звичайної мітки label. Захочемо – зробимо частину текста одним кольором, іншу частину – іншим, захочемо – зробимо частину текста більшою, іншу частину меншою, це вже залежить від фантазії. “Намалюємо” текст.
Forma.DrawString(“Ось цей текст”, RisFont, PenzelRizn, Xteksta, Yteksta);
Вказуємо, що саме написати, вказуємо, яким шрифтом, яким пензлем і вказуємо, де саме.
Зрозуміло, що готових “методів” для малювання різних геометричних фігур нам Visual Studio заготовив багато.
(далі буде, стаття не закінчена)