FlexReport for WinForms | ComponentOne
In This Topic
    FlexReportDesigner Control
    In This Topic

    The FlexReportDesigner control displays .flxr files in design mode and allows you to drag, copy, and resize report fields and sections. The advantage of using the FlexReportDesigner control in place of the FlexReportDesigner app is that you can modify the FlexReportDesigner source code to suit your needs. Using the FlexReportDesigner control, you can create your own fully functional report designer app or add certain report design functionality to your apps.

    Creating your own customized report designer is useful in many situations, for example:

    To use the FlexReportDesigner component, simply create a Windows Forms App, add a FlexReport control to the form, bind it with the report you want to edit, and set the Report property in the designer control.

    The following steps describe how to create a sample designer and implement a simple report using the C1FlexReportDesigner control. The purpose of this sample designer is to illustrate how the FlexReportDesigner control integrates with a designer application. It allows you to load and save files containing multiple reports, edit and preview reports, add and remove reports from the file, undo/redo, and modify reports.

    The following image showcases a sample designer application.

    Follow the step-by-step implementation for creating a sample designer app.

    Setup the Application

    1. Create a new Windows Forms App.
    2. Drag and drop the following controls on the form, from the ToolBox, as mentioned below:
      Control Control Name Description
      Label label1, label2 Label control displays some informative text on the UI.
      C1FlexViewerPane c1FlexViewerPane1 C1FlexViewer Pane control for previewing the reports.
      C1FlexReportDesigner c1FlexReportDesigner1 C1FlexReport Designer control for designing and editing reports.
      PropertyGrid PropertyGrid1 PropertyGrid control for editing properties of object selected in the designer.
      ListBox listReport ListBox control with a list of reports currently loaded.
      Splitter splitter1 Splitter control resizes the docked controls at run-time.
      ToolStrip toolStrip1 ToolStrip control acts like a container without adding its child controls.
      C1FlexReport c1FlexReport1 C1FlexReport component used for rendering reports into the c1FlexViewerPane1 control.

      The form contains controls such as labels and splitters, which are used to improve the layout. These are optional and may be omitted depending on your needs. The application source code implements all of these controls, and you can use the source code as a basis for your implementation.

    3. Define the class variables and constants by adding the following code to Form.cs of your project:
      C#
      Copy Code
      // fields        
      private string _fileName;     // name of the current file        
      private bool _dirty;          // current file has changed
      int _ctr = 0;
      
      // title to display in the form caption        
      private const string _appName = "FlexReportDesigner Demo";
      
    4. Modify the UI using UpdateUI method. This makes sure that the UI reflects the application's state. Also, the first call should be made in response to the Form_Load event, to initialize the toolbar and form caption.
      C#
      Copy Code
      private void Form1_Load(object sender, EventArgs e)
      {
          UpdateUI();
      }
              
      private void UpdateUI()
      {
          // update caption         
          Text = (_fileName != null && _fileName.Length > 0)
          ? string.Format("{0} - [{1}] {2}", _appName, _fileName, _dirty ? "*" : "")
          : _appName;
      
          // push/release design/preview mode buttons         
          bool design = c1FlexReportDesigner1.Visible && c1FlexReportDesigner1.Report != null;
          _btnDesign.Checked = design;
          _btnPreview.Checked = !design;
      
          // enable/disable buttons         
          _btnCut.Enabled = design && c1FlexReportDesigner1.CanCut;
          _btnCopy.Enabled = design && c1FlexReportDesigner1.CanCopy;
          _btnPaste.Enabled = design && c1FlexReportDesigner1.CanPaste;
          _btnUndo.Enabled = design && c1FlexReportDesigner1.CanUndo;
          _btnRedo.Enabled = design && c1FlexReportDesigner1.CanRedo;
      
          bool reportSelected = design && listReport.SelectedItem != null;
          _btnAddReport.Enabled = c1FlexReportDesigner1.Visible;
          _btnDelReport.Enabled = reportSelected;
          _btnAddField.Enabled = reportSelected;
          _btnAddLabel.Enabled = reportSelected;
      }
      

      Notice how UpdateUI uses the CanCut, CanPaste, CanUndo, and CanRedo properties to enable and disable toolbar buttons.

    Configure the FlexReportDesigner Toolbar

    1. Handle the toolbar button clicks and route them to the appropriate handlers using the following code.
      C#
      Copy Code
      private void toolStrip1_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
      {
          // design/preview mode        
          if (e.ClickedItem == _btnDesign) SetDesignMode(true);
          if (e.ClickedItem == _btnPreview) SetDesignMode(false);
      
          // file commands        
          if (e.ClickedItem == _btnNew) NewFile();
          if (e.ClickedItem == _btnOpen) OpenFile();
          if (e.ClickedItem == _btnSave) SaveFile();
      
          //// allow user to undo clipboard operations        
          if (e.ClickedItem == _btnCut || e.ClickedItem == _btnPaste)
              c1FlexReportDesigner1.UndoSaveState();
      
          //// clipboard        
          if (e.ClickedItem == _btnCut) c1FlexReportDesigner1.DoCut();
          if (e.ClickedItem == _btnCopy) c1FlexReportDesigner1.DoCopy();
          if (e.ClickedItem == _btnPaste) c1FlexReportDesigner1.DoPaste();
      
          //// undo/redo        
          if (e.ClickedItem == _btnUndo) c1FlexReportDesigner1.UndoUndo();
          if (e.ClickedItem == _btnRedo) c1FlexReportDesigner1.UndoRedo();
      
          //// add/remove reports        
          if (e.ClickedItem == _btnAddReport) NewReport();
          if (e.ClickedItem == _btnDelReport) DeleteReport();
      
          //// add fields        
          //// (just set create info and wait for CreateField event from designer)        
          if (e.ClickedItem == _btnAddField)
          {
              c1FlexReportDesigner1.DoCreateFieldWithMouse(e.ClickedItem);
          }
          if (e.ClickedItem == _btnAddLabel)
          {
              c1FlexReportDesigner1.DoCreateFieldWithMouse(e.ClickedItem);
          }
      }
      
         
    2. Configure the design mode using SetDesignMode method as showcased in the following code.
      C#
      Copy Code
      private void SetDesignMode(bool design)
      {
          // show/hide preview/design panes        
          c1FlexReportDesigner1.Visible = design;
          c1FlexViewerPane1.Visible = !design;
      
          // no properties in preview mode        
          if (!design)
          {
              label2.Text = "Properties";
              propertyGrid1.SelectedObject = null;
          }
      
          // attach copy of the report to preview control        
          // (so changes caused by script aren't saved)        
          if (!design)
          {
              c1FlexViewerPane1.DocumentSource = null;
             c1FlexReport1 = c1FlexReportDesigner1.Report;
              Cursor = Cursors.WaitCursor;
              c1FlexReport1.Render();
              if (c1FlexReport1.PageCount > 0)
                  c1FlexViewerPane1.DocumentSource = c1FlexReport1;
              Cursor = Cursors.Default;
          }
          // done, update UI         
          UpdateUI();
      }
      

      The application's designer control appears when the user selects a new report or clicks the Design button on the toolbar. However, when the user clicks on the Preview button, the application renders the current report into the preview control.

    Configure the Toolbar Command Buttons

    This simple designer has three commands that support files: New, Open, and Save. The following steps help you add these commands to the designer toolbar.

    1. Create the NewFile method to clear the class variables, report list, preview and designer controls, and then update the UI using the following code.
      C#
      Copy Code
      private void NewFile()
      {
          _fileName = "";
          _dirty = false;
          listReport.Items.Clear();
          c1FlexViewerPane1.DocumentSource = null;
          c1FlexReportDesigner1.Report = null;
          UpdateUI();
      }
      
    2. Create the OpenFile method to prompt the user for a report definition file to open and use the FlexReport component to retrieve a list of report names in the selected file using the following code.
      C#
      Copy Code
      public void OpenFile()
      {
          // get name of file to open        
          OpenFileDialog dlg = new OpenFileDialog();
          dlg.FileName = "*.flxr";
          dlg.Title = "Open Report Definition File";
          if (dlg.ShowDialog() != DialogResult.OK)
              return;
      
          // check selected file        
          string[] reports = null;
          try
          {
              reports = C1FlexReport.GetReportList(dlg.FileName);
          }
          catch { }
          if (reports == null || reports.Length == 0)
          {
              MessageBox.Show("Invalid (or empty) report definition file");
              return;
          }
      
          // clear list        
          NewFile();
      
          // load new file        
          Cursor = Cursors.WaitCursor;
          _fileName = dlg.FileName;
          foreach (string reportName in reports)
          {
              C1FlexReport rpt = new C1FlexReport();
              rpt.Load(_fileName, reportName);
              listReport.Items.Add(new ReportHolder(rpt));
          }
          Cursor = Cursors.Default;
          // select first report        
          listReport.SelectedIndex = 0;
      }
      
    3. Create the SaveFile method to prompt the user for a file name and use an XmlTextWriter to save each report into the new file using the following code.
      C#
      Copy Code
      public void SaveFile()
      {
          // get name of file to save        
          SaveFileDialog dlg = new SaveFileDialog();
          dlg.FileName = _fileName;
          dlg.Title = "Save Report Definition File";
          if (dlg.ShowDialog() != DialogResult.OK)
              return;
      
          // save file        
          XmlTextWriter w = new XmlTextWriter(dlg.FileName, System.Text.Encoding.Default);
          w.Formatting = Formatting.Indented;
          w.Indentation = 2;
          w.WriteStartDocument();
          // write all reports to it        
          Cursor = Cursors.WaitCursor;
          w.WriteStartElement("Reports");
          w.WriteElementString("FormatVersion", "1.0 1.0.0.0");    
          foreach (ReportHolder rh in listReport.Items)
              rh.Report.Save(w);     
          w.WriteEndElement();
          Cursor = Cursors.Default;
      
          // close the file        
          w.Close();
      
          // and be done        
          _fileName = dlg.FileName;
          _dirty = false;
          UpdateUI();
      }
      
    4. Create a ReportHolder wrapper class to store reports in listboxes as shown in the following code. This class overrides the ToString() method to render the report names in the listbox.
      C#
      Copy Code
      public class ReportHolder
      {
          public readonly C1FlexReport Report;
          public ReportHolder(C1FlexReport report)
          {
              Report = report;
          }
          override public string ToString()
          {
              string s = Report.ReportName;
              return (s != null && s.Length > 0) ? s : "Unnamed Report";
          }
      }
      

    Hook Up the Controls

    Next, add the event handlers that hook up all the controls together.

    1. Add the following code for the SelectedIndexChanged event, which deals with the display of a new report in design mode when the user chooses report from the list control. The controls are connected by means of event handlers.
      C#
      Copy Code
      private void ListReport_SelectedIndexChanged(object sender, EventArgs e)
      {
          // switch to design mode        
          SetDesignMode(true);
      
          // attach selected report to designer and preview controls        
          c1FlexReportDesigner1.Report = null;
          c1FlexViewerPane1.DocumentSource = null;
          if (listReport.SelectedItem != null)
              c1FlexReportDesigner1.Report = ((ReportHolder)listReport.SelectedItem).Report;
      }
      
    2. Implement the SelectionChanged event for the FlexReportDesigner control. Here, the designer uses a property grid control to expose the properties of the report elements selected in the designer. This is done by setting the SelectedObject property of the PropertyGrid  control. In its response the control fires a SelectionChanged event.
      C#
      Copy Code
      private void C1FlexReportDesigner1_SelectionChanged(object sender, EventArgs e)
      {
      
         // object[] sel = c1FlexReportDesigner1.SelectedFields;
          if (c1FlexReportDesigner1.SelectedFields.Count > 0)
          {
              label2.Text = "Field Properties";
              propertyGrid1.SelectedObjects = c1FlexReportDesigner1.SelectedFields.ToArray();
          }
          else if (c1FlexReportDesigner1.SelectedSubSections.Count>0)
          {
              label2.Text = "Section Properties";
              propertyGrid1.SelectedObject =c1FlexReportDesigner1.GetCurrentSection();
          }
          else if (c1FlexReportDesigner1.Report != null)
          {
              label2.Text = "Report Properties";
              propertyGrid1.SelectedObject = c1FlexReportDesigner1.Report;
          }
          else // nothing selected        
          {
              label2.Text = "Properties";
              propertyGrid1.SelectedObject = null;
          }
      
          // done        
          UpdateUI();
      }
      
    3. Implement the handler for the PropertyValueChanged event of the PropertyGrid control to refresh the designer and show changes when a value changes in the Property window using the following code. Then, implement the handler for the ValuesChanged event of the FlexReportDesigner control to update the property window and show the changes when properties of the selected objects change in the designer.
      C#
      Copy Code
      private void C1FlexReportDesigner1_ValuesChanged(object sender, ValuesChangedEventArgs e)
      {
          propertyGrid1.Refresh();
          _dirty = true;
          UpdateUI();
      }
      
      private void PropertyGrid1_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
      {
          c1FlexReportDesigner1.Refresh();
          _dirty = true;
          UpdateUI();
      }
      

    Configure Reports

    1. Create the DeleteReport method to remove the selected report from the report list and clear the Report property of the designer control using the following code.
      C#
      Copy Code
      // remove current report from the list          
      private void DeleteReport()
      {
          // a report must be selected          
          int index = listReport.SelectedIndex;
          if (index < 0) return;
      
          // remove report from the designer and from the list          
          c1FlexReportDesigner1.Report = null;
          listReport.Items.RemoveAt(index);
      
          // select another report if we can          
          if (index > listReport.Items.Count - 1)
              index = listReport.Items.Count - 1;
          if (index > -1)
              listReport.SelectedIndex = index;
      
          // done          
          _dirty = true;
          UpdateUI();
      }
      
    2. Create the NewReport method to generate a new report using the following code. The NewReport method invokes a wizard that allows the user to select a data source, grouping options, layout, and style. While implementing your designer, you can use the wizard code as-it-is or customize it to suit your needs.
      C#
      Copy Code
      private void NewReport()
      {
          // select a data source (just mdb files in this sample)          
          OpenFileDialog dlg = new OpenFileDialog();
          dlg.FileName = "*.mdb";
          dlg.Title = "Select report data source";
          if (dlg.ShowDialog() != DialogResult.OK) return;
      
          // select first table from data source          
          string connString =
                 string.Format(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};",
                 dlg.FileName);
          string tableName = GetFirstTable(connString);
          if (tableName == null || tableName.Length == 0)
          {
              MessageBox.Show("Failed to retrieve data from the selected source.");
              return;
          }
      
          // create new report          
          C1FlexReport rpt = new C1FlexReport();
          rpt.ReportName = tableName;
      
          // set data source          
          rpt.DataSource.ConnectionString = connString;
          rpt.DataSource.RecordSource = tableName;
      
          // add a title field          
          Section s = rpt.Sections[SectionTypeEnum.Header];
          s.Visible = true;
          s.Height = 600;
          TextField f = new TextField();
          f.Name = "TitleField";
          f.Text = tableName;
          f.Height = 600;
          f.Width = 4000;
          f.Top = 0;
          f.Left = 0;
          f.Font.Bold = true;
          f.Font.Size = 24;
          f.ForeColor = Color.Navy;
          s.Fields.Add(f);
      
      
          FieldInfo fld1 = (FieldInfo)(rpt.DataSource.DataSourceInfo.Fields[1]);
          FieldInfo fld2 = (FieldInfo)(rpt.DataSource.DataSourceInfo.Fields[2]);
          string[] fieldNames = new string[] { fld1.Name, fld2.Name};
          int cnt = Math.Min(5, fieldNames.Length);
      
          // add a page header          
          s = rpt.Sections[SectionTypeEnum.PageHeader];
          s.Visible = true;
          s.Height = 400;
          Rectangle rc = new Rectangle(0, 0, 1000, (int)s.Height);
          for (int i = 0; i < cnt; i++)
          {
              f = new TextField();
              f.Name = "TitleField";
              f.Text = fieldNames[i];
              f.Height = rc.Height;
              f.Width = rc.Width;
              f.Top = rc.Top;
              f.Left = rc.Left;
              f.Font.Bold = true;
              f.Font.Bold = true;
              rc.Offset(rc.Width, 0);
              s.Fields.Add(f);
          }
      
          // add detail section          
          s = rpt.Sections[SectionTypeEnum.Detail];
          s.Visible = true;
          s.Height = 300;
          rc = new Rectangle(0, 0, 1000, (int)s.Height);
          for (int i = 0; i < cnt; i++)
          {
              f = new TextField();
              f.Name = "TitleField";
              f.Text.Expression = fieldNames[i];
              f.Height = rc.Height;
              f.Width = rc.Width;        
              f.Top = rc.Top;
              f.Left = rc.Left;
              f.Font.Bold = true;
              f.Font.Bold = true;
              rc.Offset(rc.Width, 0);
              s.Fields.Add(f);
          }
      
          // add new report to the list and select it          
          listReport.Items.Add(new ReportHolder(rpt));
          listReport.SelectedIndex = listReport.Items.Count - 1;
      
          // done          
          _dirty = true;
          UpdateUI();
      }
      
    3. Create a helper method named GetFirstTable to open a connection, retrieve the DB schema and return the name of the first table it finds using the following code.
      C#
      Copy Code
      private string GetFirstTable(string connString)
      {
          string tableName = null;
          OleDbConnection conn = new OleDbConnection(connString);
          try
          {
              //get schema           
              conn.Open();
              DataTable dt = conn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
              foreach (DataRow dr in dt.Rows)
              {
                  // check the table type           
                  string type = dr["TABLE_TYPE"].ToString().ToUpper();
                  if (type != "TABLE" && type != "VIEW" && type != "LINK")
                      continue;
      
                  // get the table name           
                  tableName = dr["TABLE_NAME"].ToString();
                  break;
              }
      
              // done           
              conn.Close();
          }
          catch { }
      
          // return the first table we found           
          return tableName;
      }
      
    4. Create new fields in the report by handling the CreateField event using the following code.
      C#
      Copy Code
      private void C1FlexReportDesigner1_CreateField(object sender, CreateFieldEventArgs e)
      {
          // save undo info       
          c1FlexReportDesigner1.UndoSaveState();
      
          // add label field        
          string fieldName = string.Format("NewField{0}", ++_ctr);
          string fieldText = fieldName;
          TextField field = new TextField();
          field.Text = fieldText;
          field.Name = fieldName;
          field.Top  = e.FieldBounds.Top;
          field.Width = e.FieldBounds.Width;  
          field.Height = e.FieldBounds.Height;
          field.Left= e.FieldBounds.Left;
          e.SubSection.ParentSection.Fields.Add(field);
      
          // if this is a calculated field,         
          if (e.CreateFieldInfo == _btnAddField)
          {
              FieldInfo fld1 = (FieldInfo)(c1FlexReportDesigner1.Report.DataSource.DataSourceInfo.Fields[0]);
              field.Text.Expression = fld1.Name;             
          }
          c1FlexReportDesigner1.UpdateFromReport();
      }