1 Problem Statement
Assume that we need to develop a graphical user interface (GUI) application, which finds roots of a quadratic equation whose coefficients are functions of the parameter t.
f(t) · x2 + g(t) · x + c = 0
The functions f(t) and g(t) can be arbitrary. In our example, they will be defined as follows:
f(t) = (a0 + a1) (a1 + a2) ... (am – 1 + am) – t
g(t) = x0 y0 + x1 y1 + ...+ xn yn + t
The program should provide the following functions
- keyboard data input
- reading data from XML-file
- editing source data
- storing data in another XML-file
- calculating roots
- graphical interpretation,
- generation and storing report,
- providing minimal help information.
2 Requirements Setting. Analysis and Design
2.1 Use Case Diagram
Program requirements are represented on use case diagram.
This diagram should include one actor (user), who can interact with our program in several ways:
- creation of a new blank project for data input
- reading data from an XML file
- writing modified data to another XML file
- solving equation with graphical interpretation; solving should include discriminant checking
- report generation
- getting help.
Figure 2.1 shows use case diagram
.
2.2 Development of Data Structure and Test Preparation
Before program development we should prepare source data. According to problem setting data should be represented in the form of XML document. In our case, equation is defined by a set of numbers ai and bj, and c value.
Here is an example of XML document that describes an equation.
<?xml version="1.0"?> <EquationData CCoef="-2"> <FFunction> <ACoefs> <ACoef Value = "3" Index="0" /> <ACoef Value = "1" Index="1" /> <ACoef Value = "0" Index="2" /> <ACoef Value = "0.25" Index="3" /> </ACoefs> </FFunction> <GFunction> <XYCoefs> <XYCoef X = "-1" Y = "2" /> <XYCoef X = "1" Y = "1" /> <XYCoef X = "0.0" Y = "0.1" /> </XYCoefs> </GFunction> </EquationData>
Note: for coefficients, indices are significant; however, the order of elements in an XML document can be arbitrary, and indexes should therefore be kept separate.
We can save this data, for example, in the EquationCommon.xml
file (it describes the general case). Several file variants should be prepared for software testing. For example, if we change the value of CCoef
(-2) to any positive, for example, 2, the equation will have no solutions. We can save this option in the EquationNoSolutions.xml
file. We can also create files in which the coefficient at the second power of x
will be 0. This is LinearEquation.xml
:
<?xml version="1.0"?> <EquationData CCoef="6"> <FFunction> <ACoefs> <ACoef Value = "1" Index="0" /> <ACoef Value = "1" Index="1" /> <ACoef Value = "0" Index="2" /> <ACoef Value = "0" Index="3" /> </ACoefs> </FFunction> <GFunction> <XYCoefs> <XYCoef X = "-1" Y = "2" /> <XYCoef X = "1" Y = "1" /> <XYCoef X = "0" Y = "1" /> </XYCoefs> </GFunction> </EquationData>
As well as LinearEquationNoSolutions.xml
:
<?xml version="1.0"?> <EquationData CCoef="6"> <FFunction> <ACoefs> <ACoef Value = "1" Index="0" /> <ACoef Value = "1" Index="1" /> <ACoef Value = "0" Index="2" /> <ACoef Value = "0" Index="3" /> </ACoefs> </FFunction> <GFunction> <XYCoefs> <XYCoef X = "-1" Y = "0" /> <XYCoef X = "0" Y = "1" /> <XYCoef X = "0" Y = "1" /> </XYCoefs> </GFunction> </EquationData>
3 Creation of Essential Classes. Implementation of Console Test
3.1 Creating a Class to Represent an Equation
The .NET software solution in C# programming language that will be created will consist of a class library, a console application, and a graphical user interface application. The library will contain classes that describe the main entities of the subject area. These classes will implement the functionality of the program that is being created. To test mathematical methods, input-output facilities, and report generation, we'll create a console application. Later, a project will be created in the same solution that implements a graphical user interface application.
In MS Visual Studio, create a new project, a class library(Create a new project | Class Library) with the
name (Project name) QuadraticLib
. We set the name
Quadratic
for the solution. On the Additional Information page, in the Framework list
we select the latest version of .NET and click the Create button. We get the following source code in the Class1.cs
file:
namespace QuadraticLib {public class Class1 { } }
The class name Class1
can be changed to Quadratic
, and the file name to Quadratic.cs
Now
we can add fields, properties, and methods to the Quadratic
class. The functions f(t) and g(t) will
be represented by the corresponding delegate type properties, the coefficient will be represented by a property of
the floating point type. The roots that will be found will be written to a list. Methods for solving the equation,
getting roors, and a dor testing also needed.
Before describing the class, we can add a definition of an enumeration that determines the possible states of the "quadratic equation" object, as well as the required type of delegate.
The source code of Quadratic.cs
file will be as follows:
// Quadratic.cs namespace QuadraticLib {/// <summary> /// Enumeration that defines possible states of the "Quadratic Equation" object /// </summary> public enum EquationState { NoData, NotSolved, NoRoots, RootsFound, InfinityOfRoots }/// <summary> /// Description of the function type that takes a double type parameter /// and returns a double type value /// </summary> /// <param name="t">parameter</param> /// <returns>function value</returns> public delegate double DFunction(double t);/// <summary> /// Represents a quadratic equation. /// Coefficients a and b are determined by functions f(t) and g(t) /// </summary> public class Quadratic {/// <summary> /// Function f(t) /// </summary> public DFunction F {get ;set ; } = t => 0;/// <summary> /// Function g(t) /// </summary> public DFunction G {get ;set ; } = t => 0;/// <summary> /// Coefficient c /// The property is virtual, allowing derived classes /// to define a different way of representing data /// </summary> virtual public double C {get ;set ; }/// <summary> /// State of the equation /// </summary> public EquationState State {get ;set ; } = EquationState.NoData;/// <summary> /// The latest value of parameter t /// </summary> public double T {get ;set ; }/// <summary> /// List of equation roots /// </summary> public List<double > Roots {get ;private set ; } =new ();/// <summary> /// Solves the quadratic equation. Coefficients a and b depend on the parameter. /// After the function execution, the roots list contains two values /// (quadratic equation with a discriminant greater or equal to 0), /// one value (solvable linear equation), /// If the list is empty, there are no roots. /// Correspondingly, the State property value changes /// </summary> /// <param name="t">parameter</param> /// <returns>current object with changed state</returns> public Quadratic Solve(double t) { T = t; Roots =new ();double a = F(t);double b = G(t);if (a == 0) {if (b == 0 && C == 0) { State = EquationState.InfinityOfRoots;return this ;// Infinite number of solutions }if (b == 0 && C != 0) { State = EquationState.NoRoots;return this ;// No roots } Roots.Add(-C / b); State = EquationState.RootsFound;return this ;// One root }// Discriminant calculation: double d = b * b - 4 * a * C;if (d < 0) { State = EquationState.NoRoots;return this ;// No roots } Roots.Add((-b - Math.Sqrt(d)) / (2 * a)); Roots.Add((-b + Math.Sqrt(d)) / (2 * a)); State = EquationState.RootsFound;return this ;// Two roots }/// <summary> /// Forms a string with the equation results (two roots) /// </summary> /// <returns>string with the equation results</returns> public string GetRoots() {return "X1 = " + Roots[0] + " X2 = " + Roots[1]; } } }
Separately, it is advisable to create a class for obtaining a representation of data about equations in the form of a string:
// QuadraticToString.cs namespace QuadraticLib {public static partial class Str {public static String ToString(Quadratic quadratic) {string st = quadratic.State == EquationState.NoData || quadratic.State == EquationState.NotSolved ? "" : "t = " + quadratic.T + "\t";return st + quadratic.Stateswitch { EquationState.NoData => "No data", EquationState.NotSolved => "The equation was not solved", EquationState.InfinityOfRoots => "Infinity of Roots", _ => quadratic.Roots.Countswitch { 0 => "No roots", 1 => "Root: " + quadratic.Roots[0], 2 => "Roots: " + quadratic.GetRoots(), _ => "Unknown error", }, }; } } }
The attribute partial
In order to check the performance of this and other library classes, it is advisable to add a console application
to the solution (File | Add | New Project... | Console App) with the name (Name) QuadraticConsoleApp
.
In the project, we create a separate class that contains methods for testing the solution of the equation. It is
also advisable to define this class with the attribute partial
// EquationTests.cs using QuadraticLib;namespace QuadraticConsoleApp {/// <summary> /// Provides methods for demonstrating the console application's functionality /// </summary> static partial class ConsoleTests {/// <summary> /// Tests solving the equation for a single value of t /// </summary> /// <param name="quadratic">reference to the equation</param> /// <param name="t">parameter</param> internal static void Test(Quadratic quadratic,double t) { Console.Write(" Quadratic: "); quadratic.Solve(t); Console.WriteLine(Str.ToString(quadratic)); Console.WriteLine(); }/// <summary> /// Tests the quadratic equation in general form /// </summary> internal static void TestEquation() { Console.WriteLine("\n==========Testing the equationin its simplest form:==========");// Quadratic equation: Console.WriteLine("------General case (x^2 + x - 2)------"); ConsoleTests.Test(new Quadratic() { F = t => 1, G = t => 1, C = -2 }, 0);// value of t does not affect the result Console.WriteLine("------General case (x^2 + 2x + 1)------"); ConsoleTests.Test(new Quadratic() { F = t => 1, G = t => t, C = 1 }, 2); Console.WriteLine("------No solutions (x^2 + x + 10)------"); ConsoleTests.Test(new Quadratic() { F = t => t, G = t => t, C = 10 }, 1);// Linear equation: Console.WriteLine("------Linear equation (one root)-----"); ConsoleTests.Test(new Quadratic() { F = t => 0, G = t => 1, C = -0.5 }, 0); Console.WriteLine("------Linear equation (no roots)-----"); ConsoleTests.Test(new Quadratic() { F = t => 0, G = t => 0, C = 1 }, 0); Console.WriteLine("------Linear equation (infinite number of roots)-----"); ConsoleTests.Test(new Quadratic() { F = t => 0, G = t => 0, C = 0 }, 0); } } }
In the Main()
method of Program
class, we demonstrate of the possibilities of
solving the equation for different data:
// Program.cs using static QuadraticConsoleApp.ConsoleTests;namespace QuadraticConsoleApp {/// <summary> /// Console application for demonstrating project functions /// </summary> class Program {/// <summary> /// Application entry point. /// Sequentially tests the quadratic equation in general form /// </summary> static void Main() { TestEquation(); } } }
In order for the QuadraticConsoleApp
project to be compiled, you must add a reference to the QuadraticLib
project.
In the context menu of Solution Explorer for QuadraticConsoleApp
project we select Add |
Project Reference..., then choose the QuadraticLib
project and click OK. In addition, we
set QuadraticConsoleApp
as startup project using the context menu of Solution Explorer (Set
as Startup Project).
After the program starts we get the following results:
============Testing equation in the simplest case:============ -------General case (x^2 + x - 2)----- Quadratic: t = 0 X1 = -2 X2 = 1 -------General case (x^2 + 2x + 1)---- Quadratic: t = 2 X1 = -1 X2 = -1 -------No solutions (x^2 + x + 10)---- Quadratic: t = 1 No roots -------Lineal equation (one root)----- Quadratic: t = 0 X1 = 0.5 -------Lineal equation (no roots)----- Quadratic: t = 0 No roots -------Lineal equation (the infinite number of roots)----- Quadratic: t = 0 The infinite number of roots
3.2 Creating Classes to Represent Functions f(t) and g(t)
Now we can extend our class library with classes that are responsible for representing the functions f(t) and g(t) according
to the given task. A new class should be added to the library (In the Solution Explorer window in the context
menu of the library we choose Add | New Item... | Class). For example, this would be the class called AFunction
.
This abstract class will represent an abstract function and provide a method to test it. To the same file, we add
classes FFunction
and GFunction
. You can use the list ( ) to present the necessary function data List . Individual
pairs x and y of
the function g(t) will have the type XYCoef . The entire file can be
renamed to Functions.cs . Its content will be as follows:
Now we can add to the library classes that are responsible for representing the functions f(t) and g(t) according
to the given task. A new class called Functions
should be added to the library (In the Solution
Explorer window in
the context menu of the library we choose Add | New Item... | Class). Then you can directly delete the class
code and add a description of the IFunction
interface, which will represent the universal
function.
To the same file, we add the classes FFunction
and GFunction
. To represent the necessary
function data, we can use lists. Individual coefficients of the function f(t) will have type ACoef
.
Individual pairs x and y of
the function g(t) will have type XYCoef
. The content of the file Functions.cs
will
be as follows:
// Functions.cs namespace QuadraticLib {/// <summary> /// Abstract representation of a function and its test method /// </summary> public interface IFunction {/// <summary> /// Abstract function description /// </summary> /// <param name="t">parameter</param> /// <returns>floating point type value</returns> abstract public double Func(double t); }/// <summary> /// Coefficient of the function f(t) /// </summary> public class ACoef {// Attributes necessary for managing the XML document // during future serialization [System.Xml.Serialization.XmlAttributeAttribute()]public double Value {get ;set ; } [System.Xml.Serialization.XmlAttributeAttribute()]public int Index {get ;set ; } }/// <summary> /// Class representing the function f(t) /// </summary> public class FFunction : IFunction {/// <summary> /// List of coefficients /// </summary> public List<ACoef> ACoefs {get ;set ; } =new ();/// <summary> /// Indexer for accessing coefficients /// </summary> public ACoefthis [int index] {get => ACoefs[index];set => ACoefs[index] = value; }/// <summary> /// Returns the number of A coefficients /// </summary> public int ACount {get => ACoefs.Count; }/// <summary> /// Adds a new element to the list /// </summary> /// <param name="value">new element</param> public void AddA(double value,int index) { ACoefs.Add(new ACoef { Value = value, Index = index }); }/// <summary> /// Removes the last element from the list /// </summary> public void RemoveLastA() { ACoefs.RemoveAt(ACoefs.Count - 1); }/// <summary> /// Returns the value of a at the specified index /// </summary> /// <param name="index">index</param> /// <returns>found value</returns> public double GetValue(int index) {return new List<ACoef>(from ain ACoefswhere a.Index == indexselect a)[0].Value; }/// <summary> /// Calculates the function f(t) /// </summary> /// <param name="t">parameter</param> /// <returns>real value</returns> public double Func(double t) {double p = 1;if (ACount == 0) { p = 0; }else {for (int i = 0; i < ACount - 1; i++) p *= GetValue(i) + GetValue(i + 1); }return p - t; } }/// <summary> /// Pair of numbers X and Y /// </summary> public class XYCoef {// Attributes necessary for managing the XML document // during future serialization [System.Xml.Serialization.XmlAttributeAttribute()]public double X {get ;set ; } [System.Xml.Serialization.XmlAttributeAttribute()]public double Y {get ;set ; } }/// <summary> /// Class representing the function g(t) /// </summary> public class GFunction : IFunction {/// <summary> /// List of pairs /// </summary> public List<XYCoef> XYCoefs {get ;set ; } =new ();/// <summary> /// Indexer implementation through the list /// </summary> public XYCoefthis [int index] {get => XYCoefs[index];set => XYCoefs[index] = value; }/// <summary> /// Returns the number of pairs /// </summary> public int PairsCount {get => XYCoefs.Count; }/// <summary> /// Adds a new element to the list of pairs /// </summary> /// <param name="p">new pair</param> public void AddXY(XYCoef p) { XYCoefs.Add(p); }/// <summary> /// Adds a new element to the list of pairs /// </summary> /// <param name="x">new X value</param> /// <param name="y">new Y value</param> public void AddXY(double x,double y) { XYCoefs.Add(new XYCoef { X = x, Y = y }); }/// <summary> /// Removes the last element from the list of pairs /// </summary> public void RemoveLastPair() { XYCoefs.RemoveAt(XYCoefs.Count - 1); }/// <summary> /// Generates an iterator for iterating through pairs /// </summary> /// <returns>iterator</returns> public IEnumerator<XYCoef> GetEnumerator() {for (int i = 0; i < PairsCount; i++)yield return this [i]; }/// <summary> /// Calculates the function g(t) /// </summary> /// <param name="t">parameter</param> /// <returns>real value</returns> public double Func(double t) {double sum = 0;foreach (XYCoef pin this ) sum += p.X * p.Y;return sum + t; } } }
Now we can also add function testing. In the fileFunctionsTests.cs
we implement the
second part of the class ConsoleTests
:
// FunctionsTests.cs using QuadraticLib;namespace QuadraticConsoleApp {/// <summary> /// Provides methods for demonstrating the console application's functionality /// </summary> static partial class ConsoleTests {/// <summary> /// Outputs a table of argument and function values to the console /// </summary> /// <param name="name">function name</param> /// <param name="from">start of the interval</param> /// <param name="to">end of the interval</param> /// <param name="step">step</param> internal static void Test(IFunction f,string name,double from ,double to,double step) { Console.WriteLine("*********** " + f.GetType() + " ***********");for (double t =from ; t <= to; t += step)// Formatted output of argument and function: Console.WriteLine("t = {1} \t {0}(t) = {2}", name, t, f.Func(t)); }/// <summary> /// Tests functions that use lists to store data /// </summary> internal static void TestFunctions() { Console.WriteLine("\n========Testing functions using lists:========");// Testing the function F: FFunction fFunction =new () { ACoefs =new List<ACoef> {new () { Index = 0, Value = 1 },new () { Index = 1, Value = 2 },new () { Index = 2, Value = 3 } } }; ConsoleTests.Test(f: fFunction, name: "F",from: -5, to: 15, step: 1);// Testing the function G: GFunction gFunction =new () { XYCoefs =new List<XYCoef> {new () { X = 1, Y = 2 },new () { X = 3, Y = 4 },new () { X = 5, Y = 6 } } }; ConsoleTests.Test(f: gFunction, name: "G", from: -5, to: 15, step: 1); } } }
We add a TestFunctions()
call to the Main()
method:
static void Main() { TestEquation(); TestFunctions(); }
The function testing results that are output after testing the equation will be as follows:
========Testing functions using lists:======== *********** QuadraticLib.FFunction *********** t = -5 F(t) = 20 t = -4 F(t) = 19 t = -3 F(t) = 18 t = -2 F(t) = 17 t = -1 F(t) = 16 t = 0 F(t) = 15 t = 1 F(t) = 14 t = 2 F(t) = 13 t = 3 F(t) = 12 t = 4 F(t) = 11 t = 5 F(t) = 10 t = 6 F(t) = 9 t = 7 F(t) = 8 t = 8 F(t) = 7 t = 9 F(t) = 6 t = 10 F(t) = 5 t = 11 F(t) = 4 t = 12 F(t) = 3 t = 13 F(t) = 2 t = 14 F(t) = 1 t = 15 F(t) = 0 *********** QuadraticLib.GFunction *********** t = -5 G(t) = 39 t = -4 G(t) = 40 t = -3 G(t) = 41 t = -2 G(t) = 42 t = -1 G(t) = 43 t = 0 G(t) = 44 t = 1 G(t) = 45 t = 2 G(t) = 46 t = 3 G(t) = 47 t = 4 G(t) = 48 t = 5 G(t) = 49 t = 6 G(t) = 50 t = 7 G(t) = 51 t = 8 G(t) = 52 t = 9 G(t) = 53 t = 10 G(t) = 54 t = 11 G(t) = 55 t = 12 G(t) = 56 t = 13 G(t) = 57 t = 14 G(t) = 58 t = 15 G(t) = 59
3.3 Creating Classes to Work with XML Documents
The next step is an implementation of working with XML files for reading and saving data. The simplest
and most adequate approach is serialization. We create an XMLQuadratic
class derived from Quadratic
class.
We also add an auxiliary class EquationData
. The source code will be as follows:
// XMLQuadratic.cs using System.Xml;using System.Xml.Serialization;namespace QuadraticLib {/// <summary> /// Represents data for solving a quadratic equation /// </summary> public class EquationData {/// <summary> /// Represents the function f as a list /// </summary> public FFunction FFunction {get ;set ; } =new ();/// <summary> /// Represents the function g as a list /// </summary> public GFunction GFunction {get ;set ; } =new ();/// <summary> /// Coefficient C /// </summary> [System.Xml.Serialization.XmlAttributeAttribute()]public double CCoef {get ;set ; } }/// <summary> /// Extends the class for representing a quadratic equation /// with capabilities for reading and writing output data /// </summary> public class XMLQuadratic : Quadratic {/// <summary> /// Data for solving the quadratic equation /// </summary> public EquationData Data {get ;set ; } =new EquationData();/// <summary> /// Coefficient c /// </summary> public override double C {get => Data.CCoef;set => Data.CCoef = value; }/// <summary> /// Clears the data and resets the equation to its initial state /// </summary> public void ClearEquation() { Data =new (); State = EquationState.NoData; }/// <summary> /// Constructor /// </summary> public XMLQuadratic() { Data =new EquationData(); F = Data.FFunction.Func; G = Data.GFunction.Func; }/// <summary> /// Reads from an XML document /// </summary> /// <param name="fileName">file name</param> /// <returns>current object with changed state</returns> public XMLQuadratic ReadFromXML(string fileName) { XmlSerializer deserializer =new (typeof (EquationData));using TextReader textReader =new StreamReader(fileName); Data = (deserializer.Deserialize(textReader) as EquationData) ??new (); F = Data.FFunction.Func; G = Data.GFunction.Func; State = EquationState.NotSolved;return this ; }/// <summary> /// Writes to an XML document /// </summary> /// <param name="fileName">file name</param> public void WriteToXML(string fileName) { XmlSerializer serializer =new (typeof (EquationData));using var textWriter =new StreamWriter(fileName);using var xmlWriter = XmlWriter.Create(textWriter,new XmlWriterSettings { Indent =true }); serializer.Serialize(xmlWriter, Data); } } }
As can be seen from the code, the quadratic equation data are collected in a separate class, EquationData
.
Note. The creation of objects by calling a method like
var xmlWriter = XmlWriter.Create();
instead of calling constructor provides a more flexible approach, since such function can provide for the creation
and return of references to objects of different types. These types can be derived from the base type, defined as
the result of the Create()
function (in our case it is XmlWriter
). This is actually one
of the implementation of a design pattern called Factory Method.
In a separate class, we implement report generation in HTML format:
// Report.cs namespace QuadraticLib {/// <summary> /// Provides report generation tools /// </summary> public static class Report {/// <summary> /// Generates a report on the program's performance in HTML format /// </summary> /// <param name="fileName">file name</param> /// <param name="t">parameter t</param> public static void GenerateReport(XMLQuadratic quadratic,string fileName,double t) {using StreamWriter writer =new (fileName); writer.WriteLine("<html>\n<head>"); writer.WriteLine("<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>"); writer.WriteLine("<title>Report on Solving Quadratic Equation</title>"); writer.WriteLine("</head>"); writer.WriteLine("<body>"); writer.WriteLine("<h2>Report</h2>"); writer.WriteLine("<p>As a result of solving the quadratic equation with the following input data:</p>"); writer.WriteLine("<p>Function F:</p>"); writer.WriteLine("<table border = '1' cellpadding=4 cellspacing=0>"); writer.WriteLine("<tr>\n<th>No</th>\n<th>a</th>\n</tr>");for (int i = 0; i < quadratic.Data.FFunction.ACoefs.Count; i++) { writer.WriteLine("<tr>"); writer.WriteLine("<td>" + quadratic.Data.FFunction.ACoefs[i].Index + "</td>"); writer.WriteLine("<td>" + quadratic.Data.FFunction.ACoefs[i].Value + "</td>"); writer.WriteLine("</tr>"); } writer.WriteLine("</table>"); writer.WriteLine("<p>Function G:</p>"); writer.WriteLine("<table border = '1' cellpadding=4 cellspacing=0>"); writer.WriteLine("<tr>\n<th>No</th>\n<th>X</th>\n<th>Y</th>\n</tr>");for (int i = 0; i < quadratic.Data.GFunction.XYCoefs.Count; i++) { writer.WriteLine("<tr>"); writer.WriteLine("<td>" + (i + 1) + "</td>"); writer.WriteLine("<td>" + quadratic.Data.GFunction.XYCoefs[i].X + "</td>"); writer.WriteLine("<td>" + quadratic.Data.GFunction.XYCoefs[i].Y + "</td>"); writer.WriteLine("</tr>"); } writer.WriteLine("</table>"); quadratic.Solve(t);if (quadratic.State == EquationState.InfinityOfRoots) writer.WriteLine("<p>it was established that the equation has an infinite number of roots.</p>");else {if (quadratic.Roots.Count > 0) { writer.WriteLine("<p>the following roots were obtained: </p>"); writer.WriteLine("<p>");for (int i = 0; i < quadratic.Roots.Count; i++) writer.WriteLine(quadratic.Roots[i] + "<br>"); writer.WriteLine("</p>"); }else { writer.WriteLine("it was established that the equation has no roots"); } } writer.WriteLine("</body>\n</html>"); } } }
We add a new class test:
// XMLTests.cs using QuadraticLib;namespace QuadraticConsoleApp {/// <summary> /// Provides methods for demonstrating the console application's functionality /// </summary> static partial class ConsoleTests {/// <summary> /// Tests the quadratic equation, the data for which /// is stored in XML files /// </summary> internal static void TestEquationWithXML() { Console.WriteLine("\n==Testing the equation, the data for which is stored in XML files:=="); XMLQuadratic quadratic =new ();// No data: Console.WriteLine(quadratic);// General case: quadratic.ReadFromXML("EquationCommon.xml"); Console.WriteLine(" File: EquationCommon.xml");// Equation not solved: Console.WriteLine(Str.ToString(quadratic)); Console.WriteLine(Str.ToString(quadratic.Solve(0))); Console.WriteLine(Str.ToString(quadratic.Solve(-5))); Console.WriteLine(Str.ToString(quadratic.Solve(-10))); Report.GenerateReport(quadratic, "Common.html", -10);// No roots: Console.WriteLine(" File: EquationNoSolutions.xml"); Console.WriteLine(Str.ToString(quadratic.ReadFromXML("EquationNoSolutions.xml").Solve(0))); Report.GenerateReport(quadratic, "NoSolutions.html", 0);// Linear equation: Console.WriteLine(" File: LinearEquation.xml"); Console.WriteLine(Str.ToString(quadratic.ReadFromXML("LinearEquation.xml").Solve(0))); Report.GenerateReport(quadratic, "Linear.html", 0);// No roots: Console.WriteLine(" File: LinearEquationNoSolutions.xml"); Console.WriteLine(Str.ToString(quadratic.ReadFromXML("LinearEquationNoSolutions.xml").Solve(0))); Report.GenerateReport(quadratic, "NoSolutions.html", 0);// Infinite roots: quadratic.C = 0; Console.WriteLine(" File: Infinity.xml"); Console.WriteLine(Str.ToString(quadratic.Solve(0))); quadratic.WriteToXML("Infinity.xml"); Report.GenerateReport(quadratic, "Infinity.html", 0);// Creating an equation from scratch: quadratic.ClearEquation(); quadratic.Data.FFunction.AddA(1, 0); quadratic.Data.FFunction.AddA(0, 1); quadratic.Data.GFunction.AddXY(1, 2); quadratic.C = 1; Console.WriteLine("New equation"); Console.WriteLine(Str.ToString(quadratic.Solve(0))); } } }
Let's make changes to the method Main()
method:
static void Main() { TestEquation(); TestFunctions(); TestEquationWithXML(); }
The full output of the console application will be as follows:
============Testing equation in the simplest case:============ -------General case (x^2 + x - 2)----- Quadratic: t = 0 X1 = -2 X2 = 1 -------General case (x^2 + 2x + 1)---- Quadratic: t = 2 X1 = -1 X2 = -1 -------No solutions (x^2 + x + 10)---- Quadratic: t = 1 No roots -------Lineal equation (one root)----- Quadratic: t = 0 X1 = 0.5 -------Lineal equation (no roots)----- Quadratic: t = 0 No roots -------Lineal equation (the infinite number of roots)----- Quadratic: t = 0 The infinite number of roots ========Testing functions using lists:======== *********** QuadraticLib.FFunction *********** t = -5 F(t) = 20 t = -4 F(t) = 19 t = -3 F(t) = 18 t = -2 F(t) = 17 t = -1 F(t) = 16 t = 0 F(t) = 15 t = 1 F(t) = 14 t = 2 F(t) = 13 t = 3 F(t) = 12 t = 4 F(t) = 11 t = 5 F(t) = 10 t = 6 F(t) = 9 t = 7 F(t) = 8 t = 8 F(t) = 7 t = 9 F(t) = 6 t = 10 F(t) = 5 t = 11 F(t) = 4 t = 12 F(t) = 3 t = 13 F(t) = 2 t = 14 F(t) = 1 t = 15 F(t) = 0 *********** QuadraticLib.GFunction *********** t = -5 G(t) = 39 t = -4 G(t) = 40 t = -3 G(t) = 41 t = -2 G(t) = 42 t = -1 G(t) = 43 t = 0 G(t) = 44 t = 1 G(t) = 45 t = 2 G(t) = 46 t = 3 G(t) = 47 t = 4 G(t) = 48 t = 5 G(t) = 49 t = 6 G(t) = 50 t = 7 G(t) = 51 t = 8 G(t) = 52 t = 9 G(t) = 53 t = 10 G(t) = 54 t = 11 G(t) = 55 t = 12 G(t) = 56 t = 13 G(t) = 57 t = 14 G(t) = 58 t = 15 G(t) = 59 ==Testing the equation, the data for which is stored in XML files:== QuadraticLib.XMLQuadratic File: EquationCommon.xml The equation was not solved t = 0 Roots: X1 = -1 X2 = 2 t = -5 Roots: X1 = -0.2637626158259733 X2 = 1.2637626158259734 t = -10 Roots: X1 = -0.15712874067277094 X2 = 1.157128740672771 File: EquationNoSolutions.xml t = 0 No roots File: LinearEquation.xml t = 0 Root: 6 File: LinearEquationNoSolutions.xml t = 0 No roots File: Infinity.xml t = 0 The infinite number of roots New equation t = 0 No roots
The next stage of work (creating a GUI application) may cause some changes in the previously created classes.
2.3 Implementation of the GUI Application
The next stage of implementation is the creation of a graphical user interface application. The WPF library tools
allow us to comply with the requirements of the Model-View-Controller (MVC) design metapattern. In particular, the
classes implemented in the QuadraticLib
library define the domain model. The model is actually independent
of the user interaction method. We can offer different presentation options (View) with appropriate controllers.
These can be, for example, Windows Forms tools, a Web application, etc.
Each of the controller classes contains event handlers related to the operation of the visual elements of the user
interface. The implementation of the controller can be quite complex, since the controller itself will be responsible
for creating the necessary objects and must be aware of the location of all the necessary methods and properties in
different model objects. In order not to reproduce the complex interaction with model objects in different controllers,
it is advisable to add a separate class to model, the so-called facade. The implementation of the corresponding pattern
assumes that a single object is created, which should be an intermediary between the user interface tools and the
model objects. In our case, we add the QuadraticFacade
class to the QuadraticLib
library:
// QuadraticFacade.cs namespace QuadraticLib {/// <summary> /// Implementation of the "Facade" pattern for creating /// graphical user interface programs /// </summary> public class QuadraticFacade {// Implementation of the "Singleton" pattern private static QuadraticFacade? instance =null ;private readonly XMLQuadratic xmlQuadratic =new ();private QuadraticFacade() { }/// <summary> /// Factory method for creating an object /// </summary> /// <returns>new facade object</returns> public static QuadraticFacade GetInstance() {if (instance ==null ) { instance =new QuadraticFacade(); }return instance; }/// <summary> /// Checks if the equation has been solved /// </summary> public bool Solved {get ;private set ; }/// <summary> /// List of coefficients of the function F /// </summary> public List<ACoef> ACoefs {get => xmlQuadratic.Data.FFunction.ACoefs;set => xmlQuadratic.Data.FFunction.ACoefs = value; }/// <summary> /// List of pairs of the function G /// </summary> public List<XYCoef> XYCoefs {get => xmlQuadratic.Data.GFunction.XYCoefs;set => xmlQuadratic.Data.GFunction.XYCoefs = value; }/// <summary> /// Coefficient c /// </summary> public double C {get => xmlQuadratic.C;set {if (value != C) { xmlQuadratic.C = value; Solved =false ; } } }/// <summary> /// The latest value of parameter t /// </summary> public double T {get => xmlQuadratic.T;set {if (value != T) { xmlQuadratic.T = value; Solved =false ; } } }/// <summary> /// String with the results of solving the equation /// </summary> public string Results {get => Str.ToString(xmlQuadratic); }/// <summary> /// A second-degree polynomial for which the equation is solved /// </summary> /// <param name="x">current value of x</param> /// <returns></returns> public double Parabola(double x) {return xmlQuadratic.F(T) * x * x + xmlQuadratic.G(T) * x + C; }/// <summary> /// Clears the data and resets the equation to the necessary state /// </summary> public void DoNew() { xmlQuadratic.ClearEquation(); xmlQuadratic.State = EquationState.NotSolved; Solved =false ; }/// <summary> /// Reads from an XML document /// </summary> /// <param name="fileName">file name</param> public void ReadFromXML(string fileName) { xmlQuadratic.ReadFromXML(fileName); Solved =false ; }/// <summary> /// Writes to an XML document /// </summary> /// <param name="fileName">file name</param> public void WriteToXML(string fileName) { xmlQuadratic.WriteToXML(fileName); }/// <summary> /// Adds a new element to the list /// </summary> public void AddNewA() { xmlQuadratic.Data.FFunction.AddA(0, xmlQuadratic.Data.FFunction.ACount); Solved =false ; }/// <summary> /// Removes the last element from the list /// </summary> public void RemoveLastA() {if (xmlQuadratic.Data.FFunction.ACount > 0) { xmlQuadratic.Data.FFunction.RemoveLastA(); Solved =false ; } }/// <summary> /// Adds a new pair element to the list /// </summary> public void AddNewPair() { xmlQuadratic.Data.GFunction.AddXY(0, 0); Solved =false ; }/// <summary> /// Removes the last pair element from the list /// </summary> public void RemoveLastPair() {if (xmlQuadratic.Data.GFunction.PairsCount > 0) { xmlQuadratic.Data.GFunction.RemoveLastPair(); Solved =false ; } }public void Solve() { xmlQuadratic.Solve(T); Solved =true ; }/// <summary> /// Generates a report on the program's performance in HTML format /// </summary> /// <param name="fileName">file name</param> public void GenerateReport(string fileName) { Report.GenerateReport(xmlQuadratic, fileName, T); }// <returns>set of minimum and maximum values</returns> /// <summary> /// Calculates the boundaries for potential graph plotting /// </summary> /// <param name="xMargin">additional margin along X</param> /// <param name="yMargin">additional margin along Y</param> /// <returns></returns> public (double xMin,double xMax,double yMin,double yMax) Ranges(double xMargin,double yMargin) { xmlQuadratic.Solve(T);double xMin = xmlQuadratic.Roots.Count > 0 ? xmlQuadratic.Roots.Min() - xMargin : -xMargin;double xMax = xmlQuadratic.Roots.Count > 0 ? xmlQuadratic.Roots.Max() + xMargin : xMargin;double yFrom = Parabola(xMin);double yTo = Parabola(xMax);double yMin = Math.Min(Math.Min(yFrom, yTo), 0) - yMargin;double yMax = Math.Max(Math.Max(yFrom, yTo), 0) + yMargin;return (xMin, xMax, yMin, yMax); } } }
Next, we add a new project to the previously created solution, a WPF application. In the Solution Explorer window,
select Quadratic and in the context menu, choose the function Add
| New Project ..., then select WPF Application
with the name QuadraticWpfApp
. After clicking the Create button, an empty window with the title MainWindow
appears
on the screen. The MainWindow.xaml
file contains a description of the window elements in XML format.
First, we change the window title to "Quadratic Equation". We add an element of type Menu
to the automatically
created grid. It will be the future main menu of the application.
After the main menu, it is advisable to place another nested grid, add labels to it with the content "Function
F(t)" and "Function G(t)", below them - two data tables (DataGrid
) to which it is advisable to assign
names (x:Name
) - – DataGridF
and DataGridG
respectively. Each table will
contain two columns (DataGridTextColumn
), which it is also advisable to specify names. Below the tables,
we place a container of fixed sizes, on which the necessary buttons (Button
) and input areas (TextBox
)
will be located. It is advisable to define meaningful names for the input areas.
In the right part of the window, we place another named container Canvas
, designed to display the graph
of the function.
After
making
the necessary settings, the file MainWindow.xaml
will look like this:.
<Window x:Class="QuadraticWpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:QuadraticWpfApp" mc:Ignorable="d" Title="Quadratic Equation" Height="600" Width="800" MinHeight="360" MinWidth="377"> <Grid> <Menu VerticalAlignment="Top"> <MenuItem Header="File"> <MenuItem Header="New" /> <MenuItem Header="Open" /> <MenuItem Header="Save As..." /> <Separator/> <MenuItem Header="Exit" /> </MenuItem> <MenuItem Header="Run"> <MenuItem Header="Solve Equation" /> <MenuItem Header="Generate Report" /> </MenuItem> <MenuItem Header="Help"> <MenuItem Header="About..." /> </MenuItem> </Menu> <Grid Width="360" HorizontalAlignment="Left" Margin="0,20,0,0"> <Grid Margin="0,0,0,260"> <Label Content="Function F(t)" Margin="10,0,0,0" /> <Label Content="Function G(t)" Margin="185,0,0,0" /> <DataGrid x:Name="DataGridF" Margin="10,25,185,10" AutoGenerateColumns="False" CanUserAddRows="False" > <DataGrid.Columns> <DataGridTextColumn Header="Index" Width="70" x:Name="ColumnIndex" /> <DataGridTextColumn Header="A" Width="70" x:Name="ColumnA" /> </DataGrid.Columns> </DataGrid> <DataGrid x:Name="DataGridG" Margin="185,25,10,10" AutoGenerateColumns="False" CanUserAddRows="False" > <DataGrid.Columns> <DataGridTextColumn Header="X" Width="70" x:Name="ColumnX" /> <DataGridTextColumn Header="Y" Width="70" x:Name="ColumnY" /> </DataGrid.Columns> </DataGrid> </Grid> <Canvas Height="270" VerticalAlignment="Bottom" > <Button Content="+" Canvas.Left="60" Canvas.Top="5" Width="30" /> <Button Content="-" Canvas.Left="95" Canvas.Top="5" Width="30" /> <Button Content="+" Canvas.Left="235" Canvas.Top="5" Width="30" /> <Button Content="-" Canvas.Left="270" Canvas.Top="5" Width="30" /> <Label Content="C" Canvas.Left="5" Canvas.Top="25" /> <TextBox x:Name="TextBoxC" Canvas.Left="25" Text="0" Canvas.Top="30" Width="150" Height="20" /> <Label Content="t" Canvas.Left="180" Canvas.Top="25" /> <TextBox x:Name="TextBoxT" Canvas.Left="200" Text="0" Canvas.Top="30" Width="150" Height="20" /> <Button Content="Solve Equation" Height="40" Canvas.Left="10" Canvas.Top="60" Width="340" /> <Button Content="Generate Report" Height="40" Canvas.Left="10" Canvas.Top="110" Width="340" /> <TextBox x:Name="TextBoxResults" Height="100" Canvas.Left="10" Width="340" Canvas.Top="160" IsReadOnly="True" /> </Canvas> </Grid> <Canvas x:Name="CanvasGraph" Margin="360,18,0,0" /> </Grid> </Window>
After the first launch of the program, the main window will look like this:
Now we add event handlers to the controller (class MainWindow
). In addition to events related to menus
and buttons, it is advisable to create event handlers TextChanged
for text input elements and SizeChanged
for CanvasGraph
.
Events can be added automatically through the properties window. The file MainWindow.xaml
after adding
event handlers will look like this:
<Window x:Class="QuadraticWpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:QuadraticWpfApp" mc:Ignorable="d" Title="Quadratic Equation" Height="600" Width="800" MinHeight="360" MinWidth="377"> <Grid> <Menu VerticalAlignment="Top"> <MenuItem Header="File"> <MenuItem Header="New" Click="MenuItemNew_Click"/> <MenuItem Header="Open" Click="MenuItemOpen_Click"/> <MenuItem Header="Save As..." Click="MenuItemSave_Click"/> <Separator/> <MenuItem Header="Exit" Click="MenuItemExit_Click"/> </MenuItem> <MenuItem Header="Run"> <MenuItem Header="Solve Equation" Click="Solve_Click"/> <MenuItem Header="Generate Report" Click="Report_Click"/> </MenuItem> <MenuItem Header="Help"> <MenuItem Header="About..." Click="MenuItemAbout_Click"/> </MenuItem> </Menu> <Grid Width="360" HorizontalAlignment="Left" Margin="0,20,0,0"> <Grid Margin="0,0,0,260"> <Label Content="Function F(t)" Margin="10,0,0,0" /> <Label Content="Function G(t)" Margin="185,0,0,0" /> <DataGrid x:Name="DataGridF" Margin="10,25,185,10" AutoGenerateColumns="False" CanUserAddRows="False" > <DataGrid.Columns> <DataGridTextColumn Header="Index" Width="70" x:Name="ColumnIndex" /> <DataGridTextColumn Header="A" Width="70" x:Name="ColumnA" /> </DataGrid.Columns> </DataGrid> <DataGrid x:Name="DataGridG" Margin="185,25,10,10" AutoGenerateColumns="False" CanUserAddRows="False" > <DataGrid.Columns> <DataGridTextColumn Header="X" Width="70" x:Name="ColumnX" /> <DataGridTextColumn Header="Y" Width="70" x:Name="ColumnY" /> </DataGrid.Columns> </DataGrid> </Grid> <Canvas Height="270" VerticalAlignment="Bottom" > <Button Content="+" Canvas.Left="60" Canvas.Top="5" Width="30" Click="ButtonAddF_Click" /> <Button Content="-" Canvas.Left="95" Canvas.Top="5" Width="30" Click="ButtonRemoveF_Click" /> <Button Content="+" Canvas.Left="235" Canvas.Top="5" Width="30" Click="ButtonAddG_Click" /> <Button Content="-" Canvas.Left="270" Canvas.Top="5" Width="30" Click="ButtonRemoveG_Click" /> <Label Content="C" Canvas.Left="5" Canvas.Top="25" /> <TextBox x:Name="TextBoxC" Canvas.Left="25" Text="0" Canvas.Top="30" Width="150" Height="20" TextChanged="TextBoxC_TextChanged"/> <Label Content="t" Canvas.Left="180" Canvas.Top="25" /> <TextBox x:Name="TextBoxT" Canvas.Left="200" Text="0" Canvas.Top="30" Width="150" Height="20" TextChanged="TextBoxT_TextChanged"/> <Button Content="Solve Equation" Height="40" Canvas.Left="10" Canvas.Top="60" Width="340" Click="Solve_Click" /> <Button Content="Generate Report" Height="40" Canvas.Left="10" Canvas.Top="110" Width="340" Click="Report_Click" /> <TextBox x:Name="TextBoxResults" Height="100" Canvas.Left="10" Width="340" Canvas.Top="160" IsReadOnly="True" /> </Canvas> </Grid> <Canvas x:Name="CanvasGraph" Margin="360,18,0,0" SizeChanged="CanvasGraph_SizeChanged"/> </Grid> </Window>
To build a graph, we can create a separate class:
// GraphBuilder.cs using QuadraticLib;using System.Windows;using System.Windows.Controls;using System.Windows.Media;using System.Windows.Shapes;namespace QuadraticWpfApp {/// <summary> /// Tools for drawing the function graph /// </summary> public class GraphBuilder {private Canvas canvasGraph =new ();// Canvas on which the drawing is done private double width;// width of the display area private double height;// height of the display area private double xScale;// scale (number of points per unit) along the x-axis private double yScale;// scale (number of points per unit) along the y-axis private double x0;// x-coordinate of the origin private double y0;// y-coordinate of the origin /// <summary> /// Draws the graph with axes and grid /// </summary> /// <param name="canvasGraph">canvas on which the drawing is done</param> /// <param name="func">delegate representing the function</param> /// <param name="xMin">minimum value of x</param> /// <param name="xMax">maximum value of x</param> /// <param name="yMin">minimum value of y</param> /// <param name="yMax">maximum value of y</param> public void DrawGraph(Canvas canvasGraph, DFunction func,double xMin = -5,double xMax = 5,double yMin = -5,double yMax = 5) {this .canvasGraph = canvasGraph; width =this .canvasGraph.ActualWidth; height =this .canvasGraph.ActualHeight; xScale = width / (xMax - xMin); yScale = height / (yMax - yMin); x0 = -xMin * xScale; y0 = yMax * yScale;this .canvasGraph.Children.Clear(); DrawXGrid(xMin, xMax); DrawYGrid(yMin, yMax); DrawAxes(); DrawFunc(Brushes.Red, func); }/// <summary> /// Draws a line segment /// </summary> /// <param name="stroke">stroke type and color of the segment</param> /// <param name="x1">X-coordinate of the start of the segment</param> /// <param name="y1">Y-coordinate of the start of the segment</param> /// <param name="x2">X-coordinate of the end of the segment</param> /// <param name="y2">Y-coordinate of the end of the segment</param> private void AddLine(Brush stroke,double x1,double y1,double x2,double y2) { canvasGraph.Children.Add(new Line() { X1 = x1, X2 = x2, Y1 = y1, Y2 = y2, Stroke = stroke }); }/// <summary> /// Adds text /// </summary> /// <param name="text">text to add</param> /// <param name="x">X-coordinate of the text</param> /// <param name="y">Y-coordinate of the text</param> private void AddText(string text,double x,double y) { TextBlock textBlock =new (); textBlock.Text = text; textBlock.Foreground = Brushes.Black;// Set the coordinates of the block. "Attached" properties Canvas.SetLeft(textBlock, x); Canvas.SetTop(textBlock, y); canvasGraph.Children.Add(textBlock); }/// <summary> /// Adds axes /// </summary> void DrawAxes() { AddLine(Brushes.Black, x0, 0, x0, height); AddLine(Brushes.Black, 0, y0, width, y0); AddText("0", x0 + 2, y0 + 2); AddText("X", width - 10, y0 - 14); AddText("Y", x0 - 10, 2); }/// <summary> /// Directly draws the function graph in the specified color /// </summary> /// <param name="solidColor">brush with the specified color</param> /// <param name="func">delegate representing the function</param> void DrawFunc(SolidColorBrush solidColor, DFunction func) { Polyline polyline =new () { Stroke = solidColor, ClipToBounds =true };for (int x = 0; x < width; x++) {double dy = func((x - x0) / xScale);if (double .IsNaN(dy) ||double .IsInfinity(dy)) {continue ; }// We got a "normal" number polyline.Points.Add(new Point(x, y0 - dy * yScale)); } canvasGraph.Children.Add(polyline); }/// <summary> /// Adds a grid along the X-axis /// </summary> /// <param name="xMin">minimum value of x</param> /// <param name="xMax">maximum value of x</param> private void DrawXGrid(double xMin,double xMax) {double xStep = 1;// Grid step while (xStep * xScale < 25) { xStep *= 10; }while (xStep * xScale > 250) { xStep /= 10; }for (double dx = xStep; dx < xMax; dx += xStep) {double x = x0 + dx * xScale; AddLine(Brushes.LightGray, x, 0, x, height); AddText(string .Format("{0:0.###}", dx), x + 2, y0 + 2); }for (double dx = -xStep; dx >= xMin; dx -= xStep) {double x = x0 + dx * xScale; AddLine(Brushes.LightGray, x, 0, x, height); AddText(string .Format("{0:0.###}", dx), x + 2, y0 + 2); } }/// <summary> /// Adds a grid along the Y-axis /// </summary> /// <param name="yMin">minimum value of y</param> /// <param name="yMax">maximum value of y</param> private void DrawYGrid(double yMin,double yMax) {double yStep = 1;// Grid step while (yStep * yScale < 20) { yStep *= 10; }while (yStep * yScale > 200) { yStep /= 10; }for (double dy = yStep; dy < yMax; dy += yStep) {double y = y0 - dy * yScale; AddLine(Brushes.LightGray, 0, y, width, y); AddText(string .Format("{0:0.###}", dy), x0 + 2, y - 2); }for (double dy = -yStep; dy > yMin; dy -= yStep) {double y = y0 - dy * yScale; AddLine(Brushes.LightGray, 0, y, width, y); AddText(string .Format("{0:0.###}", dy), x0 + 2, y - 2); } } } }
It is advisable to add a separate window to display the report. Using the context menu of the project, we add a
new window(Add | Window (WPF)...), renaming it to WindowShowReport
. We add two rows (RowDefinition
)
to the main grid. We add a WebBrowser
component named ReportViewer
. We also add two buttons
to the bottom row for saving report and closing window. The file WindowShowReport.xaml
will have the
following content:
<Window x:Class="QuadraticWpfApp.WindowShowReport" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:QuadraticWpfApp" mc:Ignorable="d" Title="Program Report" Height="350" Width="600"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <!-- Main part for HTML content --> <RowDefinition Height="Auto" /> <!-- Bottom panel for buttons --> </Grid.RowDefinitions> <!-- Component for viewing HTML --> <WebBrowser x:Name="ReportViewer" Grid.Row="0" /> <!-- Panel for Save and Close buttons --> <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" Margin="10"> <Button Content="Save Report" Width="85" Margin="5,0" /> <Button Content="Close" Width="85" Margin="5,0" /> </StackPanel> </Grid> </Window>
In the MainWindow.xaml.cs
file, we add field of a type QuadraticFacade to the MainWindow
class and create an object
using the factory method QuadraticFacade.GetInstance()
. The implementation of event handlers will rely on calling
the methods of the facade class. The code in the MainWindow.xaml.cs
file will be as follows:
using QuadraticLib;using System;using System.IO;using System.Windows;using System.Windows.Controls;using System.Windows.Data;namespace QuadraticWpfApp {/// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window {private readonly QuadraticFacade facade = QuadraticFacade.GetInstance();public MainWindow() { InitializeComponent(); InitTables(); }private void MenuItemNew_Click(object sender, RoutedEventArgs e) { facade.DoNew(); TextBoxResults.Clear(); InitTables(); }private void InitTables() { InitFTable(); InitGTable(); }private void InitFTable() { DataGridF.ItemsSource =null ;// Bind DataGridF to the list of coefficients: DataGridF.ItemsSource = facade.ACoefs;// Specify which columns are bound to which properties: ColumnIndex.Binding =new Binding("Index"); ColumnA.Binding =new Binding("Value"); DrawGraph(); }private void InitGTable() { DataGridG.ItemsSource =null ;// Bind DataGridG to the list of pairs: DataGridG.ItemsSource = facade.XYCoefs;// Specify which columns are bound to which properties: ColumnX.Binding =new Binding("X"); ColumnY.Binding =new Binding("Y"); DrawGraph(); }private void MenuItemOpen_Click(object sender, RoutedEventArgs e) {// Create an open file dialog and set its properties: Microsoft.Win32.OpenFileDialog dlg =new (); dlg.InitialDirectory = System.AppDomain.CurrentDomain.BaseDirectory;// current directory dlg.DefaultExt = ".xml"; dlg.Filter = "XML Files (*.xml)|*.xml|All Files (*.*)|*.*";if (dlg.ShowDialog() ==true ) {try { facade.ReadFromXML(dlg.FileName); TextBoxC.Text = facade.C.ToString(); }catch (Exception) { MessageBox.Show("Error reading from file"); } TextBoxResults.Clear(); InitTables(); } }private void MenuItemSave_Click(object sender, RoutedEventArgs e) {// Confirm changes in the tables: DataGridF.CommitEdit(); DataGridG.CommitEdit();// Create a save file dialog and set its properties: Microsoft.Win32.SaveFileDialog dlg =new (); dlg.InitialDirectory = System.AppDomain.CurrentDomain.BaseDirectory; dlg.DefaultExt = ".xml"; dlg.Filter = "XML Files (*.xml)|*.xml|All Files (*.*)|*.*";if (dlg.ShowDialog() ==true ) {try { facade.WriteToXML(dlg.FileName); MessageBox.Show("File saved"); }catch (Exception) { MessageBox.Show("Error writing to file"); } } }private void MenuItemExit_Click(object sender, RoutedEventArgs e) { Close(); }private void MenuItemAbout_Click(object sender, RoutedEventArgs e) { MessageBox.Show("Sample semester project\n\nVersion 1.0", "About"); }private void ButtonAddF_Click(object sender, RoutedEventArgs e) {// Confirm changes in the table: DataGridF.CommitEdit();// Add a new row: facade.AddNewA(); TextBoxResults.Clear(); InitFTable(); }private void ButtonRemoveF_Click(object sender, RoutedEventArgs e) {// Confirm changes in the table: DataGridF.CommitEdit();// Remove the last row: facade.RemoveLastA(); TextBoxResults.Clear(); InitFTable(); }private void ButtonAddG_Click(object sender, RoutedEventArgs e) {// Confirm changes in the table: DataGridG.CommitEdit();// Add a new row: facade.AddNewPair(); TextBoxResults.Clear(); InitGTable(); }private void ButtonRemoveG_Click(object sender, RoutedEventArgs e) {// Confirm changes in the table: DataGridG.CommitEdit();// Remove the last row: facade.RemoveLastPair(); TextBoxResults.Clear(); InitGTable(); }private void Solve_Click(object sender, RoutedEventArgs e) { facade.C =double .Parse(TextBoxC.Text ?? "0"); facade.T =double .Parse(TextBoxT.Text ?? "0"); facade.Solve();string text = facade.Results; TextBoxResults.Text = text.Replace("\t", Environment.NewLine); DrawGraph(); }private void TextBoxC_TextChanged(object sender, TextChangedEventArgs e) {try { facade.C =double .Parse(TextBoxC.Text); DrawGraph(); }catch (FormatException) { } }private void TextBoxT_TextChanged(object sender, TextChangedEventArgs e) {try { facade.T =double .Parse(TextBoxT.Text); DrawGraph(); }catch (FormatException) { } }private void Report_Click(object sender, RoutedEventArgs e) {if (!facade.Solved) { MessageBox.Show("You need to solve the equation first!");return ; } WindowShowReport windowShowReport =new ();// Relative path to the HTML file string relativeFilePath = @"temp.html";// Convert relative path to absolute path string absoluteFilePath = Path.GetFullPath(relativeFilePath);// Use the absolute path for navigation windowShowReport.ReportViewer.Navigate(new Uri(absoluteFilePath));//windowShowReport.Facade = facade; windowShowReport.ShowDialog(); }// Redraw the graph private void DrawGraph() {if (TextBoxT !=null && TextBoxC !=null && CanvasGraph !=null ) { facade.T =double .Parse(TextBoxT.Text); facade.C =double .Parse(TextBoxC.Text); (double xMin,double xMax,double yMin,double yMax) = facade.Ranges(xMargin: 2, yMargin: 2);new GraphBuilder().DrawGraph(CanvasGraph, facade.Parabola, xMin, xMax, yMin, yMax); } }// Redraw the graph if its size changes private void CanvasGraph_SizeChanged(object sender, SizeChangedEventArgs e) { DrawGraph(); } } }
We add event handlers associated with the buttons to the window WindowShowReport
markup:
<Window x:Class="QuadraticWpfApp.WindowShowReport" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:QuadraticWpfApp" mc:Ignorable="d" Title="Program Report" Height="350" Width="600"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <!-- Main part for HTML content --> <RowDefinition Height="Auto" /> <!-- Bottom panel for buttons --> </Grid.RowDefinitions> <!-- Component for viewing HTML --> <WebBrowser x:Name="ReportViewer" Grid.Row="0" /> <!-- Panel for Save and Close buttons --> <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" Margin="10"> <Button Content="Save Report" Width="85" Margin="5,0" Click="Button_Save_Click" /> <Button Content="Close" Width="85" Margin="5,0" Click="Button_Close_Click" /> </StackPanel> </Grid> </Window>
The text of the file WindowShowReport.xaml.cs
with event handlers will be as follows:
using System;using System.Windows;using QuadraticLib;namespace QuadraticWpfApp {/// <summary> /// Controller for the WindowShowReport.xaml /// </summary> public partial class WindowShowReport : Window {private readonly QuadraticFacade facade = QuadraticFacade.GetInstance();public WindowShowReport() { InitializeComponent(); }private void Button_Save_Click(object sender, RoutedEventArgs e) { Microsoft.Win32.SaveFileDialog dlg =new (); dlg.InitialDirectory = System.AppDomain.CurrentDomain.BaseDirectory; dlg.DefaultExt = ".xml"; dlg.Filter = "HTML Files (*.html)|*.html|All Files (*.*)|*.*";if (dlg.ShowDialog() ==true ) {try { facade.GenerateReport(dlg.FileName); MessageBox.Show("Report saved"); }catch (Exception) { MessageBox.Show("Error writing to file"); } } }private void Button_Close_Click(object sender, RoutedEventArgs e) { Close(); } } }