Real-Time Chart in WinForms

In this sample we will build a line chart that reads its data in real time and gets updated each second.

Real-time WinForms Chart

We start by creating an empty WinForms project. We assume you have installed the Charting for WinForms component and you can see a list with chart components in the VisualStudio toolbox.

Drag the LineChart control and drop it on the form. You should now see references to the three libraries that you need automatically added to your project:

  • MindFusion.Charting;
  • MindFusion.Charting.WinForms;
  • MindFusion.Common;

1. Series and Data

Our chart will render time stamps at the X-axis. That’s why we choose the DateTimeSeries class to create the series for the chart. The constructor requires two lists – one with DateTime values and one with double values. They provide data for the X and Y axes respectively. We must also provide two more parameters: one for the start and one for the end of the time period.

List dates = new List();
List values = new List();

In order to mimic the real-time functionality we will use a timer. Our timer will generate a random number each second. At the beginning we will have a list with no values. Gradually, we will fill the values and once we reach the count of 100 we will start to delete the first value and add a new value at the end of the arrays.

That’s how we initialize the timer:

Random ran;
ran = new Random();         
        
Timer dataTimer = new Timer();
dataTimer.Tick += new EventHandler(GenerateData);
dataTimer.Interval = 1000;
dataTimer.Start();

Let’s look at the GenerateData method:

private void GenerateData(object sender, EventArgs e)
    {
         dates.Add(DateTime.Now);
         values.Add(2 + ran.NextDouble() * 8.0);

         if (dates.Count > 100)
         {
             dates.RemoveAt(0);
             values.RemoveAt(0);
         }
...
}

There we always add the current DateTime. Since the timer ticks every second that’s exactly what we want. Once the values are 100, we start to remove values at the beginning of the list.

Now let’s create the DateTime series. It requires parameters for the start and end of the period. We always allocate the first and last DateTime values as the bounds of the DateTime data for this series:

DateTimeSeries series = new DateTimeSeries(dates, values, dates[0], dates[dates.Count-1]);

Then we see if we have already added a Series and if so – replace it. If no Series has been added – add the new one:

if (lineChart1.Series.Count > 0)
       lineChart1.Series[0] = series;
else
       lineChart1.Series.Add(series);

By default the DateTimeSeries renders its values from the X-axis – the DateTime instances as labels at the X-axis. We can customize how they look with the DateTimeFormat property. The predefined DateTimeFormat members don’t have an option where the values are rendered as “14:23:34” as a time stamp. So, we choose as DateTimeFormat “CustomDateTime” and use the CustomDateTime to specify the format:

series.DateTimeFormat = DateTimeFormat.CustomDateTime;
series.CustomDateTimeFormat = "H:mm:ss"; 

2. The X-Axis

We want at each moment only the last 10 values to be visible. The other 90 or however they are should be rendered but the user must scroll to see them. We achieve with the MinValue and MaxValue properties of the DateTimeSeries:

series.MinValue = 0;
series.MaxValue = 0.1 * dates.Count;

In order to render only the last 10 series, we use the XAxis properties – MaxValue and MinValue Once the MaxValue of the DateTimeSeries is more than 1, which means that at least 10 values have been added, we adjust the visible range of values at the X-axis so that only the last 10 are visible:

if (series.MaxValue > 1)
  {
      lineChart1.XAxis.MaxValue = series.MaxValue;
      lineChart1.XAxis.MinValue = series.MaxValue - 1.0;
  }

We also set the Title for this axis:

lineChart1.XAxis.Title = "Time";

Let’s hide the numeric values from rendering at the top of the DateTime stamps with the ShowXCoordinates property:

lineChart1.ShowXCoordinates = false; 

3. The Y-Axis

By default the range of an axis is calculated based on the data. Let’s make our chart render the line graphics more to the beginning of the axis by increasing the MaxValue of the Y-axis while we fix the MinValue at 0:

lineChart1.YAxis.MinValue = 0;
lineChart1.YAxis.MaxValue = 20;
lineChart1.YAxis.Interval = 2;

We also change the axis Title and we use NumberFormat property to render the intervals with floating points:

lineChart1.YAxis.Title = "Visitors (in thousands)";
lineChart1.YAxis.NumberFormat = "N";

4. Grid

Let’s render vertical grid stripes. We want them to be dashed and light gray. We use GridType GridColor and GridLineStyle to customize the grid:

lineChart1.GridType = GridType.Vertical;
lineChart1.Theme.GridLineStyle = System.Drawing.Drawing2D.DashStyle.Dash;
lineChart1.Theme.GridLineColor = Color.FromArgb(192, 192, 192);

Note that GridColor and GridLineStyle are properties of the Theme property of LineChart add one extra – stop the grid stripes from moving around when the user scrolls the chart along the axis:

lineChart1.PinGrid = true;

5. Legend

The legend gets its labels from the Title property of a Series. In our case we set:

series.Title = "Server Requests";

We would like to change the legend background to make the legend easier to spot:

lineChart1.Theme.LegendBackground = new MindFusion.Drawing.SolidBrush(Color.FromArgb(120, 230, 230, 230));

We use a semi-transparent brush that let’s chart details be visible when the user moves the legend onto another chart element.

6. Line Colors

We want to render the line in red. We choose the MixedSeriesStyle class as the styling class for our application. You can use any other *SeriesStyle class that is appropriate in your case:

// assign a reb brush for the series
lineChart1.Plot.SeriesStyle = new MixedSeriesStyle()
    {  
         UniformStrokeThickness = 5,
         UniformStroke = new MindFusion.Drawing.SolidBrush(Color.Red),
         UniformFill = new MindFusion.Drawing.SolidBrush(Color.Red)
     };

With that our sample is complete. You can download the full code with the necessary charting dll-s from this link:

Download the WinForms Real-TimeChart Sample

About MindFusion Charting for WinForms: A versatile dashboard component that provides your WinForms application with the ability to create fascinating charts, interactive dashboards and practical gauges. The component combines a flexible API that allows custom combination of chart components to build any type of chart you want. You can add as many axes of any type you want, combine various chart series into a single chart with different data providers each. The control also supports pan and zoom, scroll, unlimited number of legends, grid and a dashboard panel. Linear and oval gauges complete the tool set and guarantee every feature you might need to build the perfect gauge, chart of any type or dashboard in WinForms is right at your fingertips. Learn more at https://mindfusion.eu/winforms-chart.html

Interactive Calendar With Events In JavaScript

In this blog post we will create a Google-like interactive monthly calendar where users will be able to create, edit and delete appointments in real time. We
will use the JavaScript Scheduler. Here is a screenshot of the finished application:

JavaScript Schedule That Mirrors the Google Calendar Features

JavaScript Schedule That Mirrors the Google Calendar Features

And you can run the sample online from this link.

I. Project Setup

We need a reference to the following file to start development:

  • MindFusion.Scheduling.js
  • light.css

The JavaScript file provides the scheduling functionality. The CSS file is responsible for the styling of our calendar. We create a subfolder named “themes” and we place the light.css file there.

We create an HTML file, a blank web page called GoogleSchedule and in the head section we place a reference to the CSS file:

<link href="themes/light.css" rel="stylesheet">

The reference to the JavaScript file goes at the bottom of the page, right before the closing body tag.

<a href="http://MindFusion.Scheduling.js">http://MindFusion.Scheduling.js</a>

We need a element that will represent the calendar, we create one in HTML code and assign it an id:

<div id="calendar" style="height: 100%; width: 100%;"></div>

We want the calendar to take the whole page, that’s why width and height are 100%.

II. Create and Customize the Scheduler

Now we are ready to do the real JavaScript programming of the calendar library. We create an empty JS file called “GoogleSchedule.js” and add a reference to it in the web page, at the bottom:

<a href="http://GoogleSchedule.js">http://GoogleSchedule.js</a>

In this JavaScript file we first create a mapping to the MindFusion.Scheduling namespace:

var p = MindFusion.Scheduling;

Then we create a Calendar instance using the Calendar DOM element from the web page:

// create a new instance of the calendar 
var calendar = new p.Calendar(document.getElementById("calendar"));

We set the calendar view to CalendarView .SingleMonth, which means the calendar shows one month at a time. We also set the theme that we referenced in the CSS file:

calendar.currentView = p.CalendarView.SingleMonth;
calendar.theme = "light";

Another customization we make – we use the itemSettings.titleFormat property to add a prefix before each event subject. The prefix is the start time of this event. here is how you set it:

calendar.itemSettings.titleFormat = "%s[hh:mm tt] %h";

Finally, we render the calendar:

//visualize the calendar
calendar.render();

III. Custom Form

By default, the form that renders when the user clicks on a cell or selection of cells in the calendar allows edit of the dates, but not hours. We will render our custom form, derived from BaseForm to change that. Our form will offer the users a list with hours for the selected dates.

We create a new, empty JavaScript file “TimeForm.js”. There we override the constructor of the BaseForm:

 TimeForm = function (calendar, item, type)
{
	p.BaseForm.call(this, calendar, item);

	this._id = "TimeForm";
	this._type = type;
	this.headerText = "Appointment";
	
}

As you can see from the constructor each BaseForm provides data for the calendar and the item that is created or edited. We will use that information later on. We create a prototype of our form and assign the constructor:

TimeForm.prototype = Object.create(p.BaseForm.prototype);
TimeForm.prototype.constructor = TimeForm;

TimeForm will be a simple one – it just renders a text area and two combo boxes with the possible hours for the start and end of the appointment. The combo boxes require a list with the members to be rendered. We create a list as a global variable:

var hoursList;

Then, in a new method we fill the list with the hours from 12 to 1, divided in intervals of 30 minutes:

// create an array of objects to fill the hours drop-down
TimeForm.prototype.getHourLabels = function ()
{
	hoursList = [];
	hoursList.push({ value: 0, text: "12:00am" });
	hoursList.push({ value: 1, text: "12:30am" });
	
	let index = 1;
	
	for(var i = 1; i < 12; i++)
	{
		hoursList.push({ value: index+1, text: i.toString() + ":00am" });
	    hoursList.push({ value: index+2, text: i.toString() + ":30am" });
		
		index += 2;
	}
	
	//add the first afternnon hours
	hoursList.push({ value: index + 1, text: "12:00pm" });
	hoursList.push({ value: index + 2, text: "12:30pm" });
	
	index += 2;
	
	for(i = 1; i < 12; i++)
	{
		hoursList.push({ value: index+1, text: i.toString() + ":00pm" });
	    hoursList.push({ value: index+2, text: i.toString() + ":30pm" });
		
		index += 2;
	}	
	
	return hoursList;
}

The next step is to override the drawContent function of BaseForm:

TimeForm.prototype.drawContent = function ()
{
	p.BaseForm.prototype.drawContent.call(this);

	var content = this.getContent();
.....
}

First, we create the text area:

var row = this.row();
	row.innerHTML = this.localInfo.subjectCaption;
	content.appendChild(row);
	
	// create a text-area for the item subject
	var textArea = this.createTextArea({ id: "subject", initValue: this.item.subject, events: { keydown: this._areaKeyDown} });
	textArea.element.style.width = "200px";
	this.addControl(textArea);

	row = this.row();
	row.appendChild(textArea.element);
	content.appendChild(row);

Before the text area we place a label with “Subject” on it. The createTextArea method requires several parameters. The first one is the id, then the initial content to be rendered and the events. We want the text area to be 200px wide. The row element places a row of controls in the BaseForm. We use it every time we want to start a new row with elements.

The text area reads its initial content from the value of the subject field in the item object that we receive as reference. After that we create a new row with elements: the drop-down with the hours for the start time:

       // create a drop-down list for start hours
	row = this.row();
	row.innerHTML = "Start Time";
	content.appendChild(row);

	var control = this.createDropDownList({ id: "start_time", items: this.getHourLabels(), initValue: this.getStartTimeIndex(), addEmptyValue: false });
	control.element.style.width = "200px";
	this.addControl(control);

	row = this.row();
	row.appendChild(control.element);
	content.appendChild(row);

The code is almost identical to the code for the text area, but here we call the getHourLabels method that returns the array with time values. There is one other new method: getStartTimeIndex(). It’s task is to check the start time of the item and to set the initial value of the drop-down control to that time.

Here is the method:

// get the index of the current item's index to set the value of the startTime drop-down:
TimeForm.prototype.getStartTimeIndex = function ()
{
	if (this.item != null && this.item.startTime != null)
	{
		
		let index  = this.item.startTime.__getHours() * 2;
		if(this.item.startTime.__getMinutes() > 0)
			index++;
		return index;		
		
	}
	return -1;
}

The method checks the hour value of the start time and multiplies it by two because for each hour we show two values in the drop-down: 9:00 and 9:30. If the user has selected half an hour we increment the index with 1.
We use almost the same code to create the endTime drop-down control, so we won’t discuss it here. You can check its code from the download with the source code files for this sample.

Our form needs two buttons – Save and Cancel. We create them in the drawButtons method:

// override BaseForm's drawButtons method to create form buttons
TimeForm.prototype.drawButtons = function ()
{
	var thisObj = this;

	var btnSave = this.createButton({
		id: "btnSave",
		text: this.localInfo.saveButtonCaption,
		events: { "click": function click(e)
		{
			return thisObj.onSaveButtonClick(e);
		}
		}
	});

	var btnCancel = this.createButton({
		id: "btnCancel",
		text: this.localInfo.cancelButtonCaption,
		events: { click: function click(e)
		{
			return thisObj.onCancelButtonClick(e);
		}
		}
	});

	var buttons = this.row();
	buttons.classList.add("mfp-buttons-row");
	buttons.appendChild(btnSave.element);
	buttons.appendChild(btnCancel.element);

	return buttons;
};

The BaseForm.createButton() method is similar to the methods that create text area and drop-down lists. We specify here that we will handle the click event for the two buttons. We place the buttons in a new row. Note that you don’t have to call the createButtons function anywhere – it is called automatically by BaseForm.

What happens when the user presses “Save”? Well, we’ll have to read the data from the hour drop-down controls and adjust the start and end time of the newly created Item:

TimeForm.prototype.onSaveButtonClick = function (e)
{
	// update the item with the form data
	 // update the item with the form data
   var startIndex = +this.getControlValue("start_time");
   var startTime = this.item.startTime.date.clone().addHours(startIndex * 0.5);

   var endIndex = +this.getControlValue("end_time");
   var endTime = this.item.endTime.date.clone().addHours(endIndex * 0.5);

   // if end time is specified, decrease it by one day
   if (endIndex != 0 && this.item.endTime.hour == 0)
    endTime.addDays(-1);

   // check for inconsistent start/end time
   if (startTime.valueOf() > endTime.valueOf())
         endTime = startTime.clone().addHours(1);

   // apply changes 
   this.item.subject = this.getControlValue("subject"); 
   this.item.startTime = startTime;
   this.item.endTime = endTime;

   // if a new item is created, add it to the schedule.items collection
   if (this.type === "new")
     this.calendar.schedule.items.add(this.item);

   // close the form
   this.closeForm();

   // repaint the calendar
   this.calendar.repaint(true);
};

First, we get the value of the startTime drop-down list and we calculate the amount of time to add to the startTime of an Item. A new Item always has a startTime at midnight. Then, we check the number of days the user has selected and calculate how many hours the event actually takes. We assign end time to a copy of the start time and add the calculated event duration to the value. Finally, if the event is new – we add it to the collection of item sin the schedule, if it’s old – we correct its data. Then we close the form and repaint the calendar.

IV. Using the Custom TimeForm

In order to use our custom form we have to add a reference to it in the HTML page, as we did with the other two files:

<script src="TimeForm.js" type="text/javascript"></script>

Then we edit the GoogleSchedule.js file First, we need to disable the built-in forms with the useForms property.

// disable this built-in forms for item creation and modification
calendar.useForms = false;

Then we have to handle the onSelectionEnd and onItemDoubleClick events to render our form. First, we wire up the events:

// handle the itemDoubleClick event to show the custom form for item editing
calendar.itemDoubleClick.addEventListener(handleItemDoubleClick);

// handle the selectionEnd event to show the custom form for item creation
calendar.selectionEnd.addEventListener(handleSelectionEnd);

Then we render the forms, below is the code that shows the TimeForm when a new item is created:

function handleSelectionEnd(sender, args)
{
	// create a new item with the start and end time of the selection
	var item = new p.Item();
	item.startTime = args.startTime;
	item.endTime = args.endTime;
	
	// create and show the custom form
	var form = new TimeForm(sender, item, "new");
	form.showForm();
}

We copy the start and end time of the selection from the event arguments. Then we use them in the constructor of the BaseForm.

And that’s the end of this tutorial. Here is a link to download the complete source code of the sample:

Download the Google Calendar in JavaScript Sample

About MindFusion JavaScript Scheduler: MindFusion Js Scheduler is the complete solution for all applications that need to render interactive timetables, event schedules or appointment calendars. Fully responsive, highly customizable and easy to integrate, you can quickly program the JavaScript scheduling library according to your needs. Find out more at https://mindfusion.eu/javascript-scheduler.html

Logic Model Software Demo

The logic model is a tool that helps managers evaluate the effectiveness of a program. The tool allows the user to list the input, output and outcomes of the process.

Logic Model Software Demo

Logic Model Software Demo

In our sample we have used the WinForms Diagram Control to create a sample software that lets users:

  • drag and drop tables that represent popular items in each of the three sections of the logic mode: input, output and outcomes;
  • edit the tables: add and delete rows, edit the text on rows;
  • connect table rows to illustrate the process flow.

I. Project Setup

We create a new project in Visual Studio and name it “LogicModel”. We add references to the following diagramming libraries:

MindFusion Libraries Used by the Logic Model Software

MindFusion Libraries Used by the Logic Model Software

After that we drag and drop the NodeListView control from the Toolbox and set its IconSize property to Size(50,50). This property tells the control how big the items in the NodeListView should be rendered.

nodeListView1.IconSize = new System.Drawing.Size(50, 50)

Then we drag and drop a DiagramView object that creates automatically a Diagram instance that represents the diagram. Here we need to set the AllowDrop and AllowInplaceEdit properties to “true”. This means the diagram can create new nodes from the items in the NodeListView that are dropped onto it. Then it allows the user to edit the nodes e.g. modify the text in the table cells:

this.diagramView1.AllowDrop = true;
this.diagramView1.AllowInplaceEdit = true;

Last but not least we set the Resize mode of the DiagramView to Fill, which means it will occupy all available space when the user changes the size of the application window.

II. The NodeListView

It holds TableNode instances that represent items in the logic model. Let’s look at a simple table. The first table called “Investment”. Here are the first three lines of code for it:

//the table with resources
TableNode tbResources = new TableNode();
tbResources.RedimTable(2, 4);
tbResources.Caption = "Investment";

The RedimTable method changes the number of rows and columns of the TableNode to be the ones specified as arguments. Each of the tables in the logic model has two columns – one for a bullet and one for the label. The count of rows varies.

Then we edit the text that is rendered by this TableNode:

tbResources[1, 0].Text = "People";
tbResources[1, 1].Text = "Money";
tbResources[1, 2].Text = "Technology";
tbResources[1, 3].Text = "Space";

Now is time for some styling. We set the Pen for the table, the pen for the caption and the CaptionBackBrush:

tbResources.Pen = new MindFusion.Drawing.Pen(Color.FromArgb(192, 192, 192));

tbResources.CaptionBrush = new MindFusion.Drawing.SolidBrush(Color.White);
tbResources.CaptionBackBrush = new MindFusion.Drawing.SolidBrush(Color.FromArgb(247, 150, 70));

Then we set the background of the rest of the TableNode:

tbResources.Brush = new LinearGradientBrush(Color.FromArgb(252, 213, 181), Color.FromArgb(248, 234, 223));

Each diagram item has a Tag property. This useful property holds any information you find useful. It is of type Object and you can use it to store data used by your node. In our application we use the Tag property of a TableNode to store the URL to the bullet rendered before each of its labels:

//keep the bullet as a tag
tbResources.Tag = Image.FromFile("../../Resources/orange.png");
//set an orange bullet before each label

for (int i = 0; i < tbResources.Rows.Count; i++)
     {
        tbResources[0, i].Image = Image.FromFile("../../Resources/orange.png");
        //save the row index as a tag for cells with text
        tbResources[1, i].Tag = i;
     }

Then we use the Tag property of the TableNode.Cell objects to store their row index. We will use it later when we handle events.

Finally, let’s not forget to add the new TableNode to the NodeListView:

nodeListView1.AddNode(tbResources);

The other tables are created in a similar way.

III. The Containers

The three rectangles where users drop tables are ContainerNode-s. These are special kinds of DiagramNode -s that can hold other nodes. Once placed into them, the containing nodes move with the container. Containers can resize automatically to fit all items but in our case we will forbid this.

Here is the code that creates the third container – “Outcomes”:

//the last container
ContainerNode cOutcomes = diagram1.Factory.CreateContainerNode(0, 0, 20, 20);
cOutcomes.Tag = 2;
cOutcomes.Caption = "Outcomes";

Note that we use again the Tag property this time to keep the index of the container – first, second or third e.g. 0, 1 or 2. Then we add some styling using the Pen, CaptionBrush, CaptionBackBrush and Brush properties to define the background and border of the container and its caption:

cOutcomes.Pen = new MindFusion.Drawing.Pen(Color.FromArgb(80, 80, 80));
cOutcomes.CaptionBrush = new MindFusion.Drawing.SolidBrush(Color.FromArgb(80, 80, 80));
cOutcomes.CaptionBackBrush = new MindFusion.Drawing.SolidBrush(Color.FromArgb(168, 192, 120));
cOutcomes.Brush = new LinearGradientBrush(Color.FromArgb(209, 224, 180), Color.FromArgb(235, 238, 229));

Then we set some properties that define how the ContainerNode responds to new items being added or removed:

cOutcomes.AllowAddChildren = true;
//do not allow the container to change size or be folded
cOutcomes.AutoGrow = false;
cOutcomes.AutoShrink = false;
cOutcomes.Foldable = false;

We let the container accept new children and do not allow the user to fold, which means to collapse it.

The containers are sized relatively to the client area of the DiagramView:

//resize the containers according to the current size of the diagramView
private void resizeContainers()
  {
    //convert the size of the rectangle from client measure units to diagram units
    RectangleF viewRect = diagramView1.ClientToDoc(diagramView1.ClientRectangle);
   ...
}

First we convert the size of the DiagramView to diagram units calling the ClientToDoc method. Then we need to find the ContainerNode -s of the diagram and arrange them next to each other. When we do this we use the Tag property of each container that tells us which is the order of the container and where we should pace it:

//identify the containers
foreach (DiagramNode node in diagram1.Nodes)
     {
       ContainerNode c = node as ContainerNode;

       if (c != null)
       {
          //get the index that tells us if this is the first, second or third container
          int index = (int)c.Tag;
          //relocate the container rectangle based on its index
          c.Bounds = new RectangleF(viewRect.Left + viewRect.Width * (0.02F + index * 0.34F),
          viewRect.Top + viewRect.Height * 0.02F, viewRect.Width * 0.27F, viewRect.Height * 0.96F);
       }
    }

We call this method each time the DiagramView is resized by handling the ClientSizeChanged event:

//raised when the size of the ClientRectangle has changed.
private void diagramView1_ClientSizeChanged(object sender, EventArgs e)
  {
    //adjust the size of the containeres
     resizeContainers();
     ...
  }

IV. Diagram Events

We handle the NodeCreated diagram event each time the user drops a new node from the NodeListView onto any of the ContainerNode-s:

this.diagram1.NodeCreated += new System.EventHandler(this.diagram1_NodeCreated);

The method that handles the event should do three important things: 1) resize the new TableNode so all text fits into it; 2)assign to each cell its correct Tag (e.g. row index) and 3) find the ContainerNode onto which the TableNode was placed and add it to it. Here is the first part of the method:

//raised when the user has dropped a new TableNode
private void diagram1_NodeCreated(object sender, NodeEventArgs e)
  {            
     TableNode tNode = e.Node as TableNode;

    if (tNode != null)
    {
        //adjust the node size
        resizeNode(tNode);
        //arrange the tags for the table cells
        arrangeTags(tNode);
                   
     }

The resizeNode and arrangeTags methods adjust the size of the TableNode and assign to each cell in the second column of the node its row index as a Tag. We won’t list the methods here, you can check them in the source code available for download.

We find the ContainerNode, if any onto which the TableNode was placed by checking if its top left corner is inside the container:

//find out the container onto which the node was dropped, if any
foreach (DiagramNode diagramNode in diagram1.Nodes)
  {
    if (diagramNode.ContainsPoint(e.Node.Bounds.Location))
    {
       ContainerNode container = diagramNode as ContainerNode;
       if (container != null)
       {                       
         //add the new node to its container
         container.Add(tNode);
                       
         break;
       }
 }
}

If it is within the container we add the node.

V. Context Menu

The context menu is rendered at right mouse click. We handle the CellClicked event of a diagram to show it:

//tracks when the user clicks on a table cell
diagram1.CellClicked += new EventHandler(diagram1_CellClicked);

We also declare a global TableNode.Cell clickedCell variable that keeps track of the clicked cell. We will use the data later:

//keeps the cell the user has clicked on
TableNode.Cell clickedCell = null;

In the diagram1_CellClicked event we check if the right mouse button was clicked and then show a ContextMenuStrip object:

//handles the cellClicked event
void diagram1_CellClicked(object sender, CellEventArgs e)
  {
    //if the user has clicked with the right mouse button
    if(e.MouseButton == MouseButton.Right)
      {
        //we should keep track of the clicked cell
        clickedCell = e.Cell;
        //and show the context menu
        cMenu.Show(Cursor.Position);
              
       }
  }

We handle the ItemClicked event of the ContextMenuStrip:

if (clickedCell != null)
   {
      TableNode tNode = clickedCell.Table;

      //if the user has selected to add a row
      if (e.ClickedItem.Text.Equals("Add row"))
         {
            //add a row
            tNode.AddRow();
            //assign the image URL kept as a tag to the first cell in the new row
            tNode[0, tNode.Rows.Count - 1].Image =
                 tNode.Tag as Image;
            //type in some dummy text in the text cell
            tNode[1, tNode.Rows.Count - 1].Text = "[edit text]";               
        }
..............
}

In the first part of the method we handle the case when the user has chosen to add a new row. The new row is inserted at the end of the table with the AddRow method. We use the Tag property of the TableNode which points to the location of the bullet for this table and we render it to the first cell of the new row.
If the user wants to delete a row we show first a warning message. Here we render the name of the detected row to be deleted. If the user agrees we get the index of the row using its Tag and we remove it from the TableNode:

 else if (e.ClickedItem.Text.Equals("Delete row"))
      {
          cMenu.Close();

          //display a warning, which shows which row is about to be deleted
          string message = "Do you want to delete row " + clickedCell.Text + "?";
          string caption = "Confirm Delete";
          MessageBoxButtons buttons = MessageBoxButtons.YesNo;
          DialogResult result;

          // Displays the MessageBox.
          result = MessageBox.Show(message, caption, buttons);
          //if the user has decided to delete the row
          if (result == System.Windows.Forms.DialogResult.Yes)
            {
                                        
             //get the index of the row that is to be deleted
             int rowIndex = (int)clickedCell.Tag;                        
             tNode.DeleteRow(rowIndex);                        
                        
           }   
...
}

Both actions of delete and insert of a new row require the indices of the table rows to be rearranged and the size of the table to be adjusted. We call:

//adjust the node size
resizeNode(tNode);
//arrange the table tags
arrangeTags(tNode);

VI. Links

Users can draw links between rows of the tables. To achieve this we first change the default Behavior of the DiagramView :

this.diagramView1.Behavior = MindFusion.Diagramming.Behavior.LinkTables;

Then we add some styling for the links:

//styling the links
diagram1.LinkShape = LinkShape.Cascading;
diagram1.LinkCascadeOrientation = MindFusion.Diagramming.Orientation.Horizontal;
diagram1.LinkHeadShape = ArrowHeads.Triangle;
diagram1.LinkHeadShapeSize = 4;

Here we define the shape of the links as “Cascading” and we change the default LinkHeadShape and LinkHeadShapeSize properties. Further styling of the links is done with a Theme and a DiagramLinkStyle:

//create a theme to apply additional link styling
Theme theme = new Theme();
DiagramLinkStyle style = new DiagramLinkStyle();
style.Brush = new MindFusion.Drawing.SolidBrush(Color.FromArgb(192, 192, 192));
style.Stroke = new MindFusion.Drawing.SolidBrush(Color.FromArgb(80, 80, 80));
theme.RegisterStyle(typeof(DiagramLink), style);
diagram1.Theme = theme;

There we specify the Brush for the links and the Stroke. When new links are created we want them to have 3 segments because this looks good. So, we handle the LinkCreated event to specify this:

this.diagram1.LinkCreated += new System.EventHandler(this.diagram1_LinkCreated);

..................

//raised when the user draws a link between two tableNode-s.
private void diagram1_LinkCreated(object sender, LinkEventArgs e)
  {
    //the link should have 3 segments
    e.Link.SegmentCount = 3;
  }    

With this our Logic Mode demo application is ready. The complete code for the application with all necessary libraries of the diagramming component is available for free direct download download from here:

Download the Logic Model Demo Application Source Code

About MindFusion.Diagramming for WinForms: A programming component that provides any WinForms application with a full set of features for creating and customizing all types of diagrams, flowcharts, schemes, hierarchies, trees, graphs etc. The control provides numerous ways to save and load a diagram, six auxiliary controls and more than 15 automatic graph layout algorithms. Diagram elements include scrollable tables, container nodes, multi-segment arrows, custom diagram item types and many more. Further details here.

Diagramming for WinForms is a royalty-free component, clients get 12 month upgrade subscription when buying a license. The source code is also available for purchase. Visit the buy page for a list with the current license prices.

JavaScript Database Designer with SQL Generator

We are going to use the JS flowchart library as a database design tool. We will create DB tables, add rows, connect the tables and generate SQL statements that would create the tables.

Here is a screenshot of the application:

Database Designer Application with SQL Generator

Database Designer Application with SQL Generator

I. Project Setup

We need two JavaScript libraries for the flowchart:

  • MindFusion.Common.js
  • MindFusion.Diagramming.js

We copy them in the work folder of the project, where we will put the HTML and the JavaScript code behind. Then we create an HTML file and name it DBDesign.html. There we will reference the two JavaScript libraries:

<a href="http://MindFusion.Common.js">http://MindFusion.Common.js</a>
<a href="http://MindFusion.Diagramming.js">http://MindFusion.Diagramming.js</a>

We reference those two libraries at the end of the HTML file, just before the closing tag. This way we are sure that the majority of the browsers will load the scripts correct.

We need an HTML 5 Canvas element for the diagram to draw itself onto and we create one inside a <div> tag:

<div style=”position: absolute; width: 100%; height: 100%; overflow: auto;”>
<canvas id=”diagram” width=”2100″ height=”2100″>
This page requires a browser that supports HTML 5 Canvas element.
</canvas>
</div>

It’s important to set and id for the Canvas element, that’s how we will get it in the JavaScript code behind file.

We create the JS file to be used by this project as DBDesign.js and we place it in the same directory as the two other JS files. We add a reference to it in the HTML page:

<a href="http://DBDesign.js">http://DBDesign.js</a>

II. UI Controls

The DBDesigner has a line of controls at the bottoms that provide menus – add/edit/delete row, create/delete/rename table and a button for connection info. We create them as buttons:

<div id="controls" style="height: 150px" left:="" 0;="" right:="" 401px;"="">
   <input type="button" id="btnAddRow" value="Add row" style="margin-left: 5px; margin-bottom: 2px;">
   <input type="button" id="btnEditRow" value="Edit row" style="margin-left: 5px; margin-bottom: 2px;">
   <input type="button" id="btnDeleteRow" value="Delete row" style="margin-left: 5px; margin-bottom: 2px;">
…..
</div>

We add a textarea for the generated SQL and we close the div:

<textarea id="generatedSql" style="height: 120px;width: 100%"></textarea>

When the user presses one of those buttons we show a dialog. The dialogs are forms. Here is the form that renames a table:

<div id="renameTable-dialog" title="Rename Table">
  <form>

<fieldset>
  	 <label for="renameTableCaption">Table name</label>
</fieldset>

	</form>
</div>

III. General Diagram Settings

Let’s start coding the JavaScript methods for the DBDesign application. We use the document.ready method to initialize the Diagram:

var Diagram = MindFusion.Diagramming.Diagram;

var diagram;

$(document).ready(function () {
   // create a Diagram component that wraps the "diagram" canvas
   diagram = MindFusion.AbstractionLayer.createControl(Diagram, null, null, null, $("#diagram")[0]);
……….

});

 

We use the id of the diagram Canvas that we set in the web page and now create the diagram control. Once we have it we set some properties to it:

// set some Diagram properties.
diagram.setBehavior(Behavior.LinkTables);
diagram.setAllowSelfLoops(false);
diagram.setBackBrush('#F0F0F0');
diagram.setLinkHeadShape('Triangle');
diagram.setLinkHeadShapeSize(4);
diagram.getSelection().allowMultipleSelection = false;

 

We change the default Behavior of the diagram control to “LinkTables”, which means users would be able to connect table rows. We stop users from creating self loops on tables and add some styling: the back brush is set to light gray, the head shape of links is ‘Triangle’ and we forbid the users to select multiple objects.

The styling of the diagram is done through themes. We create a theme and add to it a style for the table nodes:

// set the Diagram style.
var theme = new Theme();

var tableNodeStyle = new Style();
tableNodeStyle.setBrush({ type: 'LinearGradientBrush', color1: 'rgb(224, 233, 233)', color2: 'rgb(102, 154, 204)', angle: 30 });
tableNodeStyle.setTextColor({ type: 'SolidBrush', color: 'rgb(45, 57, 86)' });
tableNodeStyle.setStroke('rgb(192, 192, 192)');

 

The tableNodeStyle sets the brush, text color and stroke for the tables. Let’s tell the theme object that this is the style for table nodes:

theme.styles['std:TableNode'] = tableNodeStyle;

And let’s tell the diagram control that it has a theme:

diagram.setTheme(theme);

 

Link styling is done in the same way and you can find the code in the *.zip file that is available for download.

IV. Events

Handling events is the most important part of this application. We have events raised by the diagram elements and we have events that are raised by the JavaScript buttons. Let’s start with the js buttons. When the web page is loaded there is a single button active from the row of buttons available at the bottom of the page – “Create table”. In the document.ready() method we wire the button with an event:

$('#btnCreateTable').button().click(function (event) { createTable(); });

 

This event calls the createTable method that generates a TableNode instance:

function createTable() {
	// create a new table with the specified extent
	var table = diagram.getFactory().createTableNode(
				15 + tableCount * 3, 15 + tableCount * 4, 50, 60);
	table.setText("Table" + tableCount++);
	table.redimTable(2, 0);
	table.setScrollable(true);
	table.setConnectionStyle(ConnectionStyle.Rows);

	// set the first column to resize with the table
	table.getColumn(0).columnStyle = ColumnStyle.AutoWidth;

	generateSQL();
}

 

The createTableNode method accepts as arguments the x and y coordinates of the new TableNode and its width and height. We create initially the table with two columns and no rows. By default the tables can be scrolled and the links connect table rows.

The generateSQL method is a simple one – it just creates an SQL table. You can expand the sample with more complicated SQL statements but in our case we just create a table with the columns that were set to the TableNode:

function generateSQL() {
   var text = '';

   // enumerate all tables in the current diagram
   ArrayList.forEach(diagram.nodes, function (table) {
   text += "CREATE TABLE " + table.getText() + "\r\n(";

   // enumerate all rows of a table
   for (var r = 0; r < table.cells.rows; ++r) {
   // get text of cells in current row
   text += "\t" + table.getCell(0, r).getText() + " " + table.getCell(1, r).getText();
   if (r < table.cells.rows - 1)
	 text += ",\r\n";
   }
	text += "\r\n);\r\n\r\n";
   });

  $('#generatedSql')[0].innerHTML = text;
}

When the SQL text is generated we assign it to the textarea instance that we created.

V. Diagram Events

Here we will talk about the events fired by the diagram control. Once a table is created the users can double click on it to create new rows, edit or delete existing rows. This happens when we handle the nodeDoubleClicked event:

diagram.addEventListener(Events.nodeDoubleClicked, function (sender, args) {
	if (tblClicked != args.getNode()) {
		tblClicked = args.getNode();
	}
….

});

 

Here we identify the table that is clicked and then we have to decide which dialogue to show:

if (tblClicked) {
		var cellClicked = tblClicked.cellFromPoint(args.getMousePosition());
		if (cellClicked) {
			rowClicked = cellClicked.row;
			editRowOpen();
		}
		else if (tblClicked.hitTestManipulators(args.getMousePosition()) == null) {
		if (args.getMousePosition().y <= tblClicked.getBounds().y + tblClicked.getCaptionHeight())
			renameTableOpen();
			else
			addRowOpen();
		    }
		}

 

If an existing cell is clicked we open the editRow form. If the caption of the table was clicked we open the form for rename of a table. If none of those, we open the form that adds a new row.

Let’s look how the addRow dialogue opens:

function addRowOpen() {
  var table = tblClicked || diagram.getActiveItem();

  if (!table || !AbstractionLayer.isInstanceOfType(TableNode, table))
	return;

   addRowDialog.dialog("open");
}

 

the method calls the dialog method of addRowDialog. At the beginning of the js file we have declared a variable:

var addRowDialog = null

 

Then we create the addRowDialog object:

addRowDialog = $("#addRow-dialog").dialog({
		autoOpen: false,
		resizable: false,
		height: 'auto',
		width: 250,
		modal: false,
		buttons: {
			"OK": addRow,
			Cancel: function () {
				addRowDialog.dialog("close");
			}
		},
		close: function () {
			addRowType.val("NUMBER");
			addRowType.selectmenu("refresh");
			addRowForm[0].reset();
		}
	});
	addRowForm = addRowDialog.find("form").on("submit", function (event) {
		event.preventDefault();
		addRow();
	});

 

Here we create the dialog that has auto height, width of 250 and two buttons: OK and Cancel. The Cancel button closes the dialog. When the user has pressed OK the form is submitted and the addRow method is called.

The form that shows is defined in the HTML page and looks like that:

div id="addRow-dialog" title="New Field">
		<form>

<fieldset>
			<label for="addRow-fieldName">
				Field name</label>
			
			<label for="addRow-fieldType">
				Field type</label>
			
				NUMBER
				CHAR(32)
				DATE
				VARCHAR
				BLOB
			
</fieldset>

		</form>
</div>

The addRow method gets the clicked table and gets the two cells at the last row. It gets the text that was chosen in the dialog and assigns it to the cells. Then the dialog is closed and the SQL is generated once again.

function addRow() {
	var table = tblClicked || diagram.getActiveItem();

	if (!table || !AbstractionLayer.isInstanceOfType(TableNode, table))
		return;

	table.addRow();

	var lastRow = table.cells.rows - 1;

	// use the cell indexer to access cells by their column and row
	table.getCell(0, lastRow).setText(addRowName[0].value);
	table.getCell(1, lastRow).setText(addRowType[0].value);

	// close the dialog
	addRowDialog.dialog("close");

	// refresh SQL definition
	generateSQL();
}

 

And that’s the end for this tutorial. You can download the sample together with the necessary JavaScript libraries from this link:

Download the JavaScript Database Designer Application

Find out more about MindFusion JavaScript Diagram Library at https://mindfusion.eu/javascript-diagram.html

Spreadsheet Localization

In this blog post we will demonstrate how to customize the spreadsheet control so users can input in another language. We build a budget table whose data is in Korean. We will also use the latest localization capabilities of the component to add some auxiliary forms, which UI is also in Korean.

The sample uses WPF but the same methods of the control are available in Spreadsheet for WinForms.

A spreadsheet in Korean

A spreadsheet in Korean

I. General Settings

We create a new WPF project in Visual Studio and name it “BudgetAllocation”. You can drag and drop the WorkbookView control from the Toolbox if you have installed the Spreadsheet for WPF component or the WPF Pack. Another option is to add the required dll-s manually:

  • MindFusion.Spreadsheet.Wpf
  • MindFusion.Licensing
  • MindFusion.Common

In the XAML tag we add a mapping to the Spreadsheet control:

xmlns:ss="http://mindfusion.eu/spreadsheet/wpf"

Then, in the XAML file we create an instance of the MindFusion.Spreadsheet.Wpf.WorkbookView that holds a MindFusion.Spreadsheet.Wpf.Workbook:

<ss:WorkbookView x:Name=”workbookView”>
<ss:Workbook x:Name=”workbook” />
</ss:WorkbookView>

The next step is to add a new Worksheet to the Workbook and make it the active sheet:

 
var activeSheet = workbook.Worksheets.Add();
workbookView.ActiveWorksheet = activeSheet;
activeSheet.BeginInit();
......
activeSheet.EndInit();

With that we have completed the general settings for the project and is time to pay attention to the localization.

II. Localization

Spreadsheet for WPF can be localized to support different language through external XML files and the Locale property of the Workbook class. In our sample we will use Korean. First, let’s tell the control that we want to type in Korean:

workbook.Locale = new CultureInfo("ko-KR"); 

We need to change the default Font for the spreadsheet to one that does support Korean, we’ve chosen Malgun Gothic:

var globalStyle = activeSheet.CellRanges[0, 0, activeSheet.Columns.Count - 1, activeSheet.Rows.Count - 1].Style;
globalStyle.FontName = "Malgun Gothic";

The next step is to localize the UI of all forms that might appear when the user interacts with the spreadsheet. That happens through an XML file that we load and assign to the workbook:

 var file = @"../../Localization/Localization.KR.xml";
       if (file != null && File.Exists(file))
            workbook.SetLocalizationInfo(file);

The component provides ready to be used UI localization files for 6 of the most common languages in the world. In our case we use the file for Korean. You will find those files in a subfolder called Localization on your hard driver, in the installation folder of the control.

Once we’ve finished localizing the spreadsheet it’s time to add some data:

III. Data

Specifying data for each cell is very easy, it is made through the Data property of the Cell instance:

activeSheet.Cells["A2"].Data = "비율로서의 자금 분배";

A cell can contain a formula, and it is specified this way:

activeSheet.Cells["F4"].Data = "=SUM(B4:E4)";

IV. Appearance

The different cells and cell ranges in our spreadsheet have different formats and we set them through the Style property:

activeSheet.Cells["A3"].Style.FontBold = true;
activeSheet.Cells["A3"].Style.Background = new SolidColorBrush(Color.FromRgb(240, 240, 240));
activeSheet.Cells["A3"].Style.BorderBottomBrush = new SolidColorBrush(Color.FromRgb(220, 220, 220));
activeSheet.Cells["A3"].Style.BorderRightBrush = new SolidColorBrush(Color.FromRgb(220, 220, 220));

You can style a range of cells as well:

var heading = activeSheet.CellRanges["A2:G2"];

var headingStyle = heading.Style;
headingStyle.FontBold = true;
headingStyle.VerticalAlignment = MindFusion.Spreadsheet.Wpf.VerticalAlignment.Middle;
....

Cells can be merged:

heading.Merge();

Cells that render numbers can be formatted according to your needs:

var salesStyle = activeSheet.CellRanges[1, 5, 4, 5].Style;
salesStyle.FontBold = true;
salesStyle.Format = "#,##0.00;(#,##0.00)";

For some cells we use conditional formatting – that means we style them differently based on their data value:

// Set a conditional format
var format1 = salesStyle.ConditionalFormats.Add();
format1.Type = ConditionalFormatType.CellValue;
format1.Operator = ComparisonOperator.LessThan;
format1.First = "35";
format1.Style.Background = new SolidColorBrush(Color.FromArgb(255, 255, 120, 85));
format1.Style.Background.Freeze();

In this case we raise alert by painting red those cells whose value is less than 35 in cells that have column index 1 to 4 and row index 5.

V. Context Menu

We add a context menu to the Workbook control in XAML this way:

<ss:WorkbookView.ContextMenu>
<ContextMenu MenuItem.Click=”ContextMenu_Click”>
<MenuItem Header=”Delete Cells Form…” />
<MenuItem Header=”Insert Cells Form…” />
<MenuItem Header=”Worksheet Rename Form…” />
</ContextMenu>
</ss:WorkbookView.ContextMenu>

The ContextMenu has 3 items that allow the user to delete or insert cells as well to rename the worksheet. Let’s look at the event handler of the Click event:

//handle clicks on the context menu
private void ContextMenu_Click(object sender, RoutedEventArgs e)
   {
       var menu = sender as ContextMenu;
       var index = menu.Items.IndexOf(e.OriginalSource);

       switch (index)
       {
           case 0:
               {
                    new DeleteCellsForm(workbook).ShowDialog();
                    break;
                 }
            case 1:
                 {
                     new InsertCellsForm(workbook).ShowDialog();
                     break;
                 }
             case 2:
                 {
                     new WorksheetRenameForm(workbook, "New name").ShowDialog();
                     break;
                 }
          }
    }

We recognize the menu item that was clicked based on its index. After that we show the appropriate form. The InsertCellsForm, DeleteCellsForm and WorksheetRenameForm are all found in the MindFusion.Spreadsheet.Wpf.StandardForms namespace and we have to add a reference to the MindFusion.Spreadsheet.Wpf.StandardForms.dll. Once we do that, since we’ve set the localization info to point to a Korean file, those forms also show in Korean:

Spreadsheet UI in Korean

Spreadsheet UI in Korean

With that our sample is ready. You can download the full source code together with the localization file and the necessary component libraries from here:

Download Spreadsheet Localization Sample in WPF

About Spreadsheet for WPF: A native WPF component that allows developer to add to their applications functionality similar to those of Microsoft Excel. The diverse features of the component include smart API, rich styling options, support for numerous import/export formats, custom DB functions, formula and charts. Find out more about MindFusion Spreadsheet for WPF here.