Krzysztof Mossakowski
For Students
  • The simplest Windows Forms application
    • Create a new project (Visual C# / Windows / Empty Project)
    • Add a new item to the project - a class (menu Project / Add Class)
    • Replace the generated code with the following:
    •  using System;
       using System.Windows.Forms;
      
       public class HelloWorld : Form 
       {
          public static void Main() 
          {
              Application.Run(new HelloWorld());
          }
          
          public HelloWorld() 
          {
              this.Text = "Hello World!";
          }
       }		
    • Try to compile the project
      • Add a reference to Windows.Forms (menu Project / Add Reference / .NET / System.Windows.Forms)
    • Run the project - two windows appear: the console and application's
      • Change the output type of the project to 'Windows Application' (menu Project / <project_name> Properties / Application / Output type)
  • Creating Windows Forms application using Visual Studio - a simple calculator
    • Create a new project (Visual C# / Windows / Windows Application)
    • Investigate files with the source code
      • Program.cs
      • Form1.cs (right click / View Code)
      • Form1.Designer.cs (notice Windows Form Designer generated code section with the InitializeComponent method)
    • Exercise: create a simple calculator
    • Hints:
      • Change names of controls
      • Change sizes of controls (use the mouse or modify the Size property)
      • Check the functionality of the FlowLayoutPanel and TableLayoutPanel controls (from the Containers group on the Toolbox)
        • Use the Dock property
      • ComboBox.DropDownStyle
      • Use a double-click on a button to write the code
      • The code for pushing '=' button:
         int left = 0, right = 0, result = 0;
         if ( leftTextBox.Text.Length > 0 ) { 
            left = int.Parse( leftTextBox.Text );
         }
         if ( rightTextBox.Text.Length > 0 ) {
            right = int.Parse( rightTextBox.Text );
         }
         switch ( operatorComboBox.Text )
         {
            case "-": result = left - right; break;
            case "+": result = left + right; break;
            case "*": result = left * right; break;
            case "/": 
                if ( right == 0 ) {                 
                    MessageBox.Show( "Divide by 0", "Error", 
                        MessageBoxButtons.OK, MessageBoxIcon.Error );
                } else { 
                    result = left / right;
                }
                break;
         } 
         resultTextBox.Text = result.ToString();  
    • Investigate the code in the 'Windows Form Designer generated code' section
    • Add an ErrorProvider control and use it to inform about wrong numbers
    • Hints:
      • Add an event handler for the Validated event
         string s = (sender as TextBox).Text;
         if (s.Length > 0) {
             int res;
             if (!int.TryParse(s, out res)) {
                 errorProvider.SetError(resultTextBox, "Not a number!");
                 return;
             }
         }
         errorProvider.SetError(resultTextBox, "");
                    
      • Add an event handler for the Validating event (instead of that for the Validated event)
         string s = (sender as TextBox).Text;
         if (s.Length > 0) {
             int res;
             if (!int.TryParse(s, out res)) {
                 errorProvider.SetError(resultTextBox, "Not a number!");
                 e.Cancel = true;
                 return;
             }
         }
         errorProvider.SetError(resultTextBox, "");
                    
        notice the difference

  • Using controls - creating an image viewer
    • Generate a Windows Forms application
    • Add a StatusStrip control (from Menus & Toolbars group on the Toolbox)
      • Set the Name property to 'statusStrip'
    • Add a SplitContainer control (from Containers group on the Toolbox)
    • Add a ListView control to the Panel1 of the SplitContainer
      • Set the Dock property to Fill
      • Set the Name property to filesListView
    • Add a Panel control (from Containers group on the Toolbox) to the Panel2 of the SplitContainer
      • Set the Name property to 'imagePanel'
      • Set the Dock property to Fill
      • Set the BackColor property to Black
    • Compile and run the project
      • Test changing size of the window and position of the splitter
    • Add a MenuStrip control (from Menus & Toolbars group on the Toolbox)
      • Create a 'Directory' group with options: 'Change', separator, 'Exit'
      • Use '&' to underline a shortcut letter
      • Use '-' to create a separator
      • Change the default names of options to more adequate
    • Add a FolderBrowserDialog control (Toolbox: Dialogs)
    • Add an event handler for Directory / Change option
       if ( folderBrowserDialog.ShowDialog() == DialogResult.OK ) {
          try {
              filesListView.Items.Clear();
              DirectoryInfo di = new DirectoryInfo( folderBrowserDialog.SelectedPath );
              Environment.CurrentDirectory = folderBrowserDialog.SelectedPath;
              FileInfo []files = di.GetFiles();
              foreach ( FileInfo file in files ) {
                  filesListView.Items.Add( file.Name );
              }
          }
          catch ( Exception ) {
          } 
      }  
    • Add using statements to compile this code successfully (use the MSDN for help or Visual Studio assitance)
    • Add a ContextMenuStrip control
      • Set the Name property to 'filesContextMenuStrip'
      • Add two items: 'Large Icon', 'List'
      • Add event handlers for Click event
         filesListView.View = View.List;
      • Set filesListView.ContextMenuStrip to filesContextMenuStrip
    • Run and test a context menu
    • Add a PictureBox control to the imagePanel
      • Set the Dock property to Fill
      • Set the Visible property to False
      • Set the Name property to pictureBox
    • Add a Label to imagePanel
      • Set the Text property to 'Not an image!'
      • Set the ForeColor property to White
      • Set the Dock property to Fill
      • Set the TextAlign property to MiddleCenter
      • Set the AutoSize property to False
      • Change the Font property to Verdana 24pt, bold
      • Set the Visible property to False
      • Set the Name property to notImageLabel
    • Modify the filesListView
      • Set the MultiSelect property to False
      • Add an event handler for the SelectedIndexChanged event
         ListView.SelectedListViewItemCollection selected = filesListView.SelectedItems;
         if ( selected.Count == 0 ) {
            pictureBox.Visible = notImageLabel.Visible = false;
         } else {
            string file = selected[0].Text;
            try {
                pictureBox.Image = Image.FromFile( file );
                pictureBox.Visible = true;
                notImageLabel.Visible = false;
            } 
            catch ( Exception ) {
                pictureBox.Visible = false;
                notImageLabel.Visible = true;
            }
         }  
    • Run and test the application
    • Modify the statusStrip
      • Add a StatusLabel control
        • Set the Name property to 'nameToolStripStatusLabel'
        • Clear the Text property
        • Set the TextAlign property to MiddleLeft
        • Set the Spring property to true
      • Add a StatusLabel
        • Set the Name property to 'sizeToolStripStatusLabel'
        • Clear the Text property
        • Set the BorderSides property to All
        • Set the BorderStyle property to SunkenOuter
      • Add the following code to the filesListView_SelectedIndexChanged() method
         nameToolStripStatusLabel.Text = Environment.CurrentDirectory + '\\' + file;
         sizeToolStripStatusLabel.Text = string.Format( "{0} x {1}", 
            pictureBox.Image.Width, pictureBox.Image.Height );  
    • Scrolling an image
      • Set the imagePanel.AutoScroll to True
      • Add the following code to filesListView_SelectedIndexChanged()
         imagePanel.AutoScrollMinSize = new Size(
            pictureBox.Image.Width, pictureBox.Image.Height );  

  • Drawing and using dialogs
    • Add Effects / Grayscale option to the menu
       if ( pictureBox.Image != null ) {
          Cursor = Cursors.WaitCursor;
          Color clr;
          int v;
          Bitmap bmp = new Bitmap( pictureBox.Image );
          for ( int x = 0; x < bmp.Width; x++ ) {
              for ( int y = 0; y < bmp.Height; y++ ) {
                  clr = bmp.GetPixel( x, y );
                  v = ( clr.R + clr.G + clr.B ) / 3;
                  bmp.SetPixel( x, y, Color.FromArgb( v, v, v ));
              }
          }
          pictureBox.Image = bmp;
          Cursor = Cursors.Arrow;
       }  
    • Add a new form to the project
      • Set the name of the class to 'AboutForm'
      • Add Help / About option to the menu
         AboutForm about = new AboutForm();
         about.ShowDialog();  
      • Run and test
      • Set the StartPosition property to CenterParent
      • Set the ShowInTaskbar property to False
      • Set the FormBorderStyle property to FixedDialog
      • Set the MinimizeBox property to False
      • Set the MaximizeBox property to False
    • Drawing in the About form
      • Add an event handler for the Paint event (add the System.Drawing.Drawing2D namespace)
         e.Graphics.FillRectangle( Brushes.White, 0, 0, 
            ClientRectangle.Width, ClientRectangle.Height );
        
         Font font = new Font( "Verdana", 52, FontStyle.Bold );
         Brush specialBrush = new LinearGradientBrush( ClientRectangle,
            Color.Yellow, Color.Red, 45, false );
         StringFormat sf = (StringFormat)StringFormat.GenericDefault.Clone();
         sf.Alignment = StringAlignment.Center;
         sf.LineAlignment = StringAlignment.Center;
         e.Graphics.DrawString( "I'm painting!", font, specialBrush,
            ClientRectangle.Right/2, ClientRectangle.Bottom/2, sf );  
    • Add an animation of moving ball to drawing
      • Add members to the AboutForm class
         const int ballRadius = 50; 
         Point ballCenter = new Point( ballRadius, ballRadius );
         int ballSpeed = 5; 
      • Add a Timer control to the AboutForm (Toolbox: Components)
      • Add an event handler for the Tick event
         ballCenter.X += ballSpeed;
         if ( ballCenter.X < ballRadius ) {
            ballCenter.X = ballRadius;
            ballSpeed = Math.Abs( ballSpeed );
         } else if ( ballCenter.X > ClientRectangle.Width - ballRadius ) {
            ballCenter.X = ClientRectangle.Width - ballRadius;
            ballSpeed = -Math.Abs( ballSpeed );
         }
         Invalidate();  
      • Add the following code to the AboutForm_Paint method:
         e.Graphics.FillEllipse( Brushes.Black, 
            ballCenter.X - ballRadius, ballCenter.Y - ballRadius,
            2*ballRadius, 2*ballRadius );  
      • Add an event handler for the AboutForm.Load and call timer.Start()
      • Run and test
    • Prevent flickering
      • Add the following code to the AboutForm_Load method:
         DoubleBuffered = true;

  • Using a background worker thread - make the application responsive during grayscaling
    • Add a member to the form
       private Bitmap bmpToGrayscale = null;
    • Add a BackgroundWorker component to the form (Toolbox: Components)
      • Set the Name property to 'backgroundWorker'
      • Add an event handler for the DoWork event
         Color clr;
         int v;
         for (int x = 0; x < bmpToGrayscale.Width; x++) {
            for (int y = 0; y < bmpToGrayscale.Height; y++)  {
                clr = bmpToGrayscale.GetPixel(x, y);
                v = (clr.R + clr.G + clr.B) / 3;
                bmpToGrayscale.SetPixel(x, y, Color.FromArgb(v, v, v));
            }
         } 
      • Add an event handler for the RunWorkerCompleted event
         pictureBox.Image = bmpToGrayscale;
         grayscaleToolStripMenuItem.Enabled = true; 
    • Modify the grayscaleToolStripMenuItem_Click method
       if (pictureBox.Image != null) {
          bmpToGrayscale = new Bitmap(pictureBox.Image);
          grayscaleToolStripMenuItem.Enabled = false;
          backgroundWorker.RunWorkerAsync();
       }  
    • Exercise: show progress of the operation on the status strip
  • Create your own control
    • Create a new project - Visual C# / Windows / Windows Control Library
    • Change the control's base class from UserControl to Control
      • If the control had been viewed with designer before its base class was changed line of source code like this
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        had been generated for you
      • Since Control has no AutoScaleMode property that line has to be removed
    • Set the class name to 'MyControl' (use the 'Refactoring -> Rename...' option from the context menu)
    • Add members to the control
       private Point[] points = new Point[4];
      
       public Point[] Points {
          get {
              return points;
          }
          set {
              points = value;
              Invalidate();
          } 
       }
      and initialization code to the constructor
       points[0].X = 100;
       points[0].Y = 100;
       points[1].X = 100;
       points[1].Y = 200;
       points[2].X = 200;
       points[2].Y = 200;
       points[3].X = 200;
       points[3].Y = 100;
    • override the OnPaint method
       base.OnPaint(e);
       GraphicsPath gp = new GraphicsPath();
       gp.AddClosedCurve(points);
       e.Graphics.DrawPath(Pens.Black, gp);
       gp.Dispose();
       for(int i = 0; i < 4; i++) {
           e.Graphics.FillRectangle(Brushes.Red, Points[i].X - 5, Points[i].Y - 5, 10, 10);
       }
    • Add the code to make the points possible to move by mouse dragging now (add System.Drawing.Drawing2D namespace)
       private int x, y;
       private int movePoint = -1;
      
       protected override void OnMouseDown(MouseEventArgs e) {
          x = e.X;
          y = e.Y;
          if(e.Button == MouseButtons.Left) {
              movePoint = PointByXY(e.X, e.Y);
              this.Capture = false;
          }
          base.OnMouseDown(e);
       }
       
       protected override void OnMouseMove(MouseEventArgs e) {
          if(movePoint >= 0 && e.Button == MouseButtons.Left) {
              Points[movePoint].X = e.X;
              Points[movePoint].Y = e.Y;
              Invalidate();
          }
          base.OnMouseMove(e);
       }
      
       protected override void OnMouseUp(MouseEventArgs e) {
          if(movePoint >= 0 && e.Button == MouseButtons.Left) {
              Points[movePoint].X = e.X;
              Points[movePoint].Y = e.Y;
              movePoint = -1;
              Invalidate();
          }
          base.OnMouseUp(e);
       }
       
       internal int PointByXY(int x, int y) {
          for(int i = 0; i < points.Length; i++) {
              if(x >= Points[i].X - 5 && x <= Points[i].X + 5 &&
                 y >= Points[i].Y - 5 && y <= Points[i].Y + 5) {
                 
                  return i;
              }
          }
          return -1;
       }
    • Create another Windows Application project to test your control
      • Drag your control to the new form similar to a standard windows forms control
        • Notice that all controls from the current solution are automatically added to the toolbox
        • Each control is added to a tab named 'XXX Components', where XXX is a name of a project in which the control exists
    • Create an editor class
      • Add a new class named 'MyEditor'
      • Use this code for the class (System.ComponentModel, System.Drawing.Design and System.Windows.Forms.Design namespaces have to be added)
         public class MyEditor : UITypeEditor {
            private IWindowsFormsEditorService edSvc = null;
            public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) {
                if(context != null && context.Instance != null && provider != null) {
                    edSvc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
                    if(edSvc != null) {
                        MyControl orgControl = (MyControl)context.Instance;
                        MyControl propertiesControl = new MyControl();
        
                        propertiesControl.Width = orgControl.Width;
                        propertiesControl.Height = orgControl.Height;
                        Array.Copy(orgControl.Points, propertiesControl.Points, 4);
        
                        edSvc.DropDownControl(propertiesControl);
                        return propertiesControl.Points;
                    }
                }
                return value;
            }
        
            public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) {
                return UITypeEditorEditStyle.DropDown;
            }
         }
      • Add attributes for the Points property in MyControl class (add System.Drawing.Design namespace)
         [Browsable(true)]
         [Category("Image")]
         [Editor(typeof(MyEditor), typeof(UITypeEditor))]
      • Try to use your control
    • Create a designer class
      • Add a new class named 'MyDesigner'
      • Change the base class to ControlDesigner (the System.Design assembly has to be referenced, the System.Windows.Forms.Design namespace has to be added)
      • Add a member to the control
         private MyControl component;
         private int movePoint = -1;
        and initialization code
         public override void Initialize(IComponent component) {
            this.component = component as MyControl;
            base.Initialize(component);
         }
      • Add the code to make the points possible to move through the designer now (System.ComponentModel, System.Drawing and System.Windows.Forms namespaces have to be added)
         protected override void OnMouseDragBegin(int x, int y) {
            if (MyControl.MouseButtons == MouseButtons.Left) {
                Point p = component.PointToClient(new Point(x, y));
                movePoint = component.PointByXY(p.X, p.Y);
                if(movePoint >= 0) {
                    component.Capture = true;
                }
            }
            if (movePoint < 0) {
                base.OnMouseDragBegin(x, y);
            }
         }
        
         protected override void OnMouseDragMove(int x, int y) {
            if (movePoint >= 0) {
                Point p = component.PointToClient(new Point(x, y));
                component.Points[movePoint].X = p.X;
                component.Points[movePoint].Y = p.Y;
                component.Invalidate();
            }
            else {
                base.OnMouseDragMove(x, y);
            }
         }
        
         protected override void OnMouseDragEnd(bool cancel) {
            if(movePoint >= 0) {
                movePoint = -1;
                component.Capture = false;
                TypeDescriptor.GetProperties(component)["Points"].SetValue(component, component.Points);
            }
            else {
                base.OnMouseDragEnd(cancel);
            }
         }
      • Notice that control's property has to be updated with the PropertyDescriptor.SetValue() method (obtained using the TypeDescriptor.GetProperties() method)
      • Add an attribute for MyControl class
         [Designer(typeof(MyDesigner))]
      • Try to modify your control using the designer
      • Another interesting method of the ControlDesigner class is OnPaintAdornments(), explore it at home :)
    • Create a type converter class
      • Add a new class named 'MyTypeConverter'
      • Change the base class to TypeConverter (add System.ComponentModel namespace)
      • Add the overrided methods that allow non-standard conversion to and from string type (add System.Drawing namespace)
         public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
            if(sourceType == typeof(string)) {
                return true;
            }
            else {
                return base.CanConvertFrom(context, sourceType);
            }
         }
        
         public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
            if(destinationType == typeof(string)) {
                return true;
            }
            else {
                return base.CanConvertTo(context, destinationType);
            }
         }
        
         public override object ConvertFrom(ITypeDescriptorContext context, 
                System.Globalization.CultureInfo culture, object value) {
                
            if(value is string) {
                try {
                    string[] pointsString = ((string)value).Split(',');
                    List<Point> points = new List<Point>();
                    foreach(string s in pointsString) {
                        string tmp = s.TrimStart('(', ' ').TrimEnd(')', ' ');
                        string[] pointCoordinates = tmp.Split(';');
                        points.Add(new Point(int.Parse(pointCoordinates[0]), int.Parse(pointCoordinates[1])));
                    }
                    return points.ToArray();
                }
                catch(Exception ex) {
                    throw new FormatException(ex.Message, ex);
                }
            }
            else {
                return base.ConvertFrom(context, culture, value);
            }
         }
        
         public override object ConvertTo(ITypeDescriptorContext context, 
                System.Globalization.CultureInfo culture, object value, Type destinationType) {
                
            if(destinationType == typeof(string)) {
                try {
                    Point[] points = (Point[])value;
                    StringBuilder sb = new StringBuilder();
                    for(int i = 0; i < points.Length; i++) {
                        sb.AppendFormat("({0}; {1})", points[i].X, points[i].Y);
                        if(i < points.Length - 1) {
                            sb.Append(',');
                        }
                    }
                    return sb.ToString();
                }
                catch(Exception ex) {
                    throw new FormatException(ex.Message, ex);
                }
            }
            else {
                return base.ConvertTo(context, culture, value, destinationType);
            }
         }
      • Add attribute for the Points property in MyControl class
         [TypeConverter(typeof(MyTypeConverter))]
      • Inspect the Points property in 'Properties' tab, try to modify the coordinates of the points with string representation
    • Adding control to toolbox
      • Icon assigning
        • Create an icon bitmap (16 x 16, 16 colors) - example icon
        • Include the icon file into the control's project (for example using 'Add -> Existing Item...' option from project's context menu)
        • Change icon's 'Build Action' to 'Embedded Resource' (in file's properties tab)
        • Add an attribute for MyControl class
           [ToolboxBitmap(typeof(MyControl), "tool.bmp")]
      • Adding the control to toolbox
        • Select 'Choose Items...' option from toolbox's contex menu
        • Press 'Browse...' button on the dialog and choose control's library file
        • A new control should be visible on the list
        • Press 'OK' to confirm an operation, control will be visible in the toolbox now