Table of Contents
Jednoduchý kreslící program - výběr barev
https://gitlab.fjfi.cvut.cz/culikzde/pw
Přejdeme do “design režimu” (záložka DrawFrom.cs [design] nebo Shift-F7)
Odstranění chyby, pokud se do "design režimu" nelze přepnout
Pokud se místo "design" okna ohlásí chyba jako např. DrawForm does not contain a definition for colorPanel_Paint, otevřte soubor DrawForm.Designer.cs (např. poklepáním na zprávu o chybě) a odstraňte řádku odkazující na colorPanel_Paint.
// odstraňte řádku odkazující na již vymazanou funkci (např. colorPanel_Paint) this.colorPanel.Paint += new System.Windows.Forms.PaintEventHandler(this.colorPanel_Paint);
Chyba nejspíše vznikla tak, že jste v designu dvakrát "kliknuli" na barevný panel, vznikla funkce void colorPanel_Paint (...) { ... }, kterou jste asi vymazali, ale v DrawForm.Designer.cs zůstala reakce na kliknutí,
Reakce na kliknutí v barevném panelu
- Přidáme ColorDialog z Toolboxu, ze skupiny Dialogs
- ColorDialog vhodíme kamkoliv do okna, zástupná ikonka se stejně sama umístí až pod oknem
- Přejmenujeme na colorDialog (s malým písmenem na začátku, bez jedničky na konci) (v Properties řádka (name))
- Barevný dialog použijeme pro změnu barvy barevného (v našem příkladě modrého) panelu
- Vyberem náš modrý colorPanel, v Properties vybereme události (events, ikona s bleskem)
- Dvakrát ťukneme do prázdného druhého sloupce na řádce MouseDown
- Vznikne prázdná funkce
private void colorPanel_MouseDown (object sender, MouseEventArgs e) { }
- Hlavičku takovéto funkce sami nepište, ale vždy “naklikejte” v “design režimu”, jinak by chyběla vazba podobná té kterou jsem odstraňovali v minulém odstavci.
- Nyní již můžete, třeba s pomocí schránky, doplnit tělo funkce.
private void colorPanel_MouseDown (object sender, MouseEventArgs e) { Panel panel = sender as Panel; // Panel panel = (Panel) sender; if (e.Button == MouseButtons.Left && Control.ModifierKeys != Keys.Shift) { pen = new Pen (panel.BackColor); } else if (e.Button == MouseButtons.Right) { brushColor = panel.BackColor; brush = new SolidBrush (brushColor); } else if (e.Button == MouseButtons.Middle || e.Button == MouseButtons.Left && Control.ModifierKeys == Keys.Shift) { colorDialog.Color = panel.BackColor; if (colorDialog.ShowDialog () == DialogResult.OK) { panel.BackColor = colorDialog.Color; pen = new Pen (panel.BackColor); } } }
Parametr sender obsahuje odkaz na prvek, který událost způsobil.
Nyní to může být pouze náš colorPanel, ale později přidáme další panely s jinými barvami.
A tak musíme zjistit na který panel bylo kliknuto.
Sender je typu object, z kterého jsou odvozené všechny další třídy v jazyce C#.
Proměnná typu object může obsahovat odkaz (ukazatel) na libovolný objekt.
Typ je příliš obecný, neumožňuje nám pracovat např. s barvou panelu.
Parametr musíme přetypovat, máme dva možné zápisy.
Přetypování (typ) výraz
Panel p = (Panel) sender;
Přetypování (typ) výraz je co do zápisu podobné jazyku C,
tvoří ho jméno typu v kulatých závorkách a za ním přetypovávaný výraz, který v závorkch být nemusí.
Na rozdíl od jazyka C, pokud za běhu programu nejde výraz přetypovat na uvedený typ, vznikne výjimka.
Přetypování výraz as typ
Panel p = sender as Panel;
Přetypováví výraz as typ
pokud za běhu programu výraz nejde přetypovat na uvedený typ, je výsledkem null.
Relace výraz is typ
if (sender is Panel) { ... }
Relace výraz is typ
vrací true pokud lze výraz přetypovat.
Levé tlačítko myši - barva pro kreslení čar a okrajů
Pokud bylo stisknuto levé tlačítlo myši, vezmeme současnou barvu panelu a vytvoříme “tužku” zadané barvy, odkaz na ni uložíme do proměnné pen
if (e.Button == MouseButtons.Left) { pen = new Pen (panel.BackColor); }
Pravé tlačítko - barva výplně
Při stisku pravého tlačítka barvu uložíme pro pozdější použití do proměnné brushColor a vytvoříme jednobarevný “štětec”.
else if (e.Button == MouseButtons.Right) { brushColor = panel.BackColor; brush = new SolidBrush (brushColor); }
Prostřední tlačítko - dialogové okno pro výběr barvy panelu
Pokud stiskneme prostřední tlačítko (kolečko na myši) změníme barvu panelu.
K tomu použijeme již na začátku kapitoly připravený dialog pro výběr barvy.
Metoda ShowDialog nabídne uživateli okénko pro výběr barvy, výsledkem funkce je OK pokud si uzživatel vybral barvu.
Většinou se nedoboručuje funkce s vedlejším účinkem volat v podmínce if ( ),
ale při zobrazení dialogového okna to bývá tolerováno.
if (colorDialog.ShowDialog () == DialogResult.OK) { }
Barevný dialog inicializujeme na současnou barvu panelu.
Pokud si uživatel vybral barvu, změníme také barvu “tužky”, což uživatel nejsíše předpokládá.
else if (e.Button == MouseButtons.Middle) { colorDialog.Color = panel.BackColor; if (colorDialog.ShowDialog () == DialogResult.OK) { panel.BackColor = colorDialog.Color; pen = new Pen (panel.BackColor); } }
Pokud myš nemá prostřední tlačítko, můžeme použít Shift a levé tlačítko.
Vraťe se k zdrojovému kódu celé metody colorPanel_MouseDown a podívejte se na Control.ModifierKeys a Keys.Shift.
Šířka čar
Přidáme NumericUpDown
- pojmenujeme ho penWidthNumeric
- nastavíme Minimum na 1
- nastavíme Maximum na 10
- “ukotvení” k pravému okraji: vlastnost Anchor na Top, Right
Můžeme přidat Label
- pojmenujeme ho penWidthLabel
- Text změníme na pen width
- Anchor nastavíme na Top, Right
Dvojklikem na penWidthNumeric přídáme metodu
private void penWidthNumeric_ValueChanged (object sender, EventArgs e) { pen.Width = (int) penWidthNumeric.Value; }
Upravíme část programu ve funkci colorPanel_MouseDown
if (e.Button == MouseButtons.Left && Control.ModifierKeys != Keys.Shift) { pen = new Pen (panel.BackColor, (int) penWidthNumeric.Value); } ... else if (e.Button == MouseButtons.Middle || e.Button == MouseButtons.Left && Control.ModifierKeys == Keys.Shift) { ... pen = new Pen (panel.BackColor, (int) penWidthNumeric.Value); }
Pokud Vám nevyhovuje změna barvy tužky při změně barvy panelu, tak poslední přiřazení zakomentujte
Prvek NumericUpDown jako hodnotu může obsahovat i desetinná čísla, a proto musíme Value převédst na int.
Pro připomenutí počáteční červená tužka a žlutý štětec jsou přednastaveny v konstruktoru
pen = new Pen (Color.Red); brushColor = Color.Yellow; brush = new SolidBrush (brushColor);
Menu
- Přidáme MenuStrip z toolboxu ze skupiny Menus & Toolbars
- Přejmenujeme na mainMenu
Menu se umístí někam na nevhodné místo.
Zobrazíme si Document Outline a myší posuneme pořadí prvků.
(v hlavním menu Visual Studia View / Other windows / Document Outline nebo Ctrl+Alt+T)
Uvnitř okna DrawForm by mělo být pořadí prvků:
- pictureBox
- toolPanel
- mainMenu
- colorDialog
První by měl být hlavního prvek uprostřed okna, a až za ním další prvky směrem k okraji okna
V právě přidaném menu klikneme do políčka Type Here
- Přidáme text &File, znak & se projeví v podobě podtrženého písmene F a můžeme používat klávesovou zkratku Alt-F.
- Přejmenujeme fileToolStripMenuItem na fileMenu (jako obvykle v Properties řádka name)
Pod “File” menu opět nalezneme Type Here.
- Přidáme text &Quit, znak & nám v sub-menu umožní používat písmeno Q pro “zrychlenou volbu”.
- Přejmenujeme na quitMenuItem
Vpravo od “File” opět nalezneme Type Here.
- Přidáme text &Edit
- Přejmenujeme editMenu
- Můžeme nastavit vlastnost ShortcutKeys na Ctrl+Q ( po vyplnění malého dialogu opět klikněte do druhého sloupce tabulky, vpravo od ShortcutKey, aby se nastavení neztratilo)
Pod “Edit” menu.
- Přidáme text add &Color
- Přejmenujeme na addColorMenuItem
- Můžeme nastavit ShortcutKeys na F9
Ve vytvořeném menu “File” dvakrát klikneme na námi přidanou položku “Quit”, vznikne metoda quitMenuItem_Click
private void quitMenuItem_Click (object sender, EventArgs e) { Close (); }
Funkce Close je deklarována uvnitř třídy Form, ze které je naše třída DrawForm odvozena.
Volání Close () je ekvivalentní this.Close () a uzavře naše okno a v tomto případě skončí i celý program.
Více barevných panelů
Dvakrát klikneme ve vytvořeném menu “Edit” na položku “add Color”
private void addColorMenuItem_Click (object sender, EventArgs e) { addColorPanel (Color.Orange); }
V konstruktoru také přidáme několik barevných panelů
public DrawForm () { InitializeComponent (); // modry panel jiz existuje addColorPanel (Color.Yellow); addColorPanel (Color.Red); addColorPanel (Color.Green); pen = new Pen (colorPanel.BackColor); // modra barva podle prvniho panelu brushColor = Color.Yellow; brush = new SolidBrush (brushColor); /* ... */ }
A zbývá napsat funkci addColorPanel, tu musíme napsat celou.
private int panelCnt = 1; void addColorPanel (Color c) { Panel p = new Panel (); // vytvorime novy panel p.BackColor = c; // novy panel umistime vpravo od soucasnych panelu p.Left = colorPanel.Left + panelCnt * (colorPanel.Width + colorPanel.Left); panelCnt++; p.Top = colorPanel.Top; // ze vzoroveho panelu zkopirujeme souradnici y a velikost p.Width = colorPanel.Width; p.Height = colorPanel.Height; p.MouseDown += colorPanel_MouseDown; // pridame reakci na stisknuti mysi p.Parent = toolPanel; // pridame do toolPanelu }
- Vytvoříme nový panel voláním new Panel ()
- Ale až p.Parent = toolPanel; novy panel zobrazi
- Nastavíme různé vlastnosti, např. p.BackColor = c;
- Některé vlastnosti zkopírujeme ze vzorového colorPanelu
- Přidáme reakci na stisk myši p.MouseDown += colorPanel_MouseDown;
Celý program
using System; using System.Drawing; using System.Windows.Forms; namespace Draw { public partial class DrawForm : Form { private Pen pen; private Brush brush; private Color brushColor; private enum Shape { line, rectangle, ellipse }; public DrawForm () { InitializeComponent (); // modry panel jiz existuje addColorPanel (Color.Yellow); addColorPanel (Color.Red); addColorPanel (Color.Green); pen = new Pen (colorPanel.BackColor); // modra barva podle prvniho panelu brushColor = Color.Yellow; brush = new SolidBrush (brushColor); comboBox.SelectedIndex = (int)Shape.line; int w = pictureBox.Width; int h = pictureBox.Height; Bitmap b = new Bitmap (w, h); Graphics g = Graphics.FromImage (b); Brush br = new SolidBrush (Color.White); g.FillRectangle (br, 0, 0, w, h); pictureBox.Image = b; } private void quitMenuItem_Click (object sender, EventArgs e) { Close (); } private int panelCnt = 1; void addColorPanel (Color c) { Panel p = new Panel (); // vytvorime novy panel p.BackColor = c; // novy panel umistime vpravo od soucasnych panelu p.Left = colorPanel.Left + panelCnt * (colorPanel.Width + colorPanel.Left); panelCnt++; p.Top = colorPanel.Top; // ze vzoroveho panelu zkopirujeme souradnici y a velikost p.Width = colorPanel.Width; p.Height = colorPanel.Height; p.MouseDown += colorPanel_MouseDown; // pridame reakci na stisknuti mysi p.Parent = toolPanel; // pridame do toolPanelu } private void addColorMenuItem_Click (object sender, EventArgs e) { addColorPanel (Color.Orange); } private void colorPanel_MouseDown (object sender, MouseEventArgs e) { Panel panel = sender as Panel; // Panel panel = (Panel) sender; if (e.Button == MouseButtons.Left && Control.ModifierKeys != Keys.Shift) { pen = new Pen (panel.BackColor, (int) penWidthNumeric.Value); } else if (e.Button == MouseButtons.Right) { brushColor = panel.BackColor; brush = new SolidBrush (brushColor); } else if (e.Button == MouseButtons.Middle || e.Button == MouseButtons.Left && Control.ModifierKeys == Keys.Shift) { colorDialog.Color = panel.BackColor; if (colorDialog.ShowDialog () == DialogResult.OK) { panel.BackColor = colorDialog.Color; pen = new Pen (panel.BackColor, (int) penWidthNumeric.Value); } } } private void penWidthNumeric_ValueChanged (object sender, EventArgs e) { pen.Width = (int)penWidthNumeric.Value; } private int X0, Y0; private bool press = false; private Image save; private void pictureBox_MouseDown (object sender, MouseEventArgs e) { X0 = e.X; Y0 = e.Y; press = true; save = new Bitmap (pictureBox.Image); } private void pictureBox_MouseMove (object sender, MouseEventArgs e) { if (press) { Graphics g = Graphics.FromImage (pictureBox.Image); g.DrawImage (save, 0, 0); Shape inx = (Shape) comboBox.SelectedIndex; if (inx == Shape.line) { g.DrawLine (pen, X0, Y0, e.X, e.Y); } else if (inx == Shape.rectangle) { int X1 = X0; int Y1 = Y0; int X2 = e.X; int Y2 = e.Y; if (X2 < X1) { int t = X1; X1 = X2; X2 = t; } if (Y2 < Y1) { int t = Y1; Y1 = Y2; Y2 = t; } g.FillRectangle (brush, X1, Y1, X2 - X1, Y2 - Y1); g.DrawRectangle (pen, X1, Y1, X2 - X1, Y2 - Y1); } else if (inx == Shape.ellipse) { g.FillEllipse (brush, X0, Y0, e.X - X0, e.Y - Y0); g.DrawEllipse (pen, X0, Y0, e.X - X0, e.Y - Y0); } pictureBox.Invalidate (); } } private void pictureBox_MouseUp (object sender, MouseEventArgs e) { press = false; } } }