====== 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 [[https://cs.wikipedia.org/wiki/Vedlej%C5%A1%C3%AD_%C3%BA%C4%8Dinek|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**. [[https://gitlab.fjfi.cvut.cz/culikzde/pw/-/blob/3ea205ac/Draw/DrawForm.cs|stav z tohoto okamžiku na gitlab]] ===== Šíř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** {{draw_colors.png}} 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** {{draw_menu.png}} 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 **colorPanel**u * 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; } } } {{draw_butons.png}} [[https://gitlab.fjfi.cvut.cz/culikzde/pw/-/blob/d87f4a73/Draw/DrawForm.cs|stav z tohoto okamžiku na gitlab]]