Programmers who prefer the classic object-oriented approach will not be happy with most markup languages, whether XAML or html. The visual designers in Visual Studio don’t provide inheritance, but rather support template technologies.
In converting large VFP projects to C#, we are inevitably faced with this. We have always rewritten visual class trees in VFP as reams of WPF or JavaScript code. Complex interfaces could only be imagined. I eventually decided that we needed a better way.
"DataOdyssey WPF" and "DataOdyssey JS"" are Visual Studio extensions that can be used instead of the XAML and HTML designers. They are built as VSIX packages. To use them, you must be using Visual Studio 2022 version 17.8 or higher.
They do not fully implement the behavior of Visual Studio documents, and a few familiar mechanisms, like a “Save” button or navigation using “Go To”, are missing. But they can be very useful in working with complex class trees that require inheritance.
Two ways that you can create a new visual class are analogous to techniques available in VFP:
And in addition you can
Open a solution in Visual Studio and right click on the folder where you want to create your DataOdyssey visual controls library. Select “Add”, then “DataOdyssey Visual Class.”
This dialog will appear when you create a new DataOdyssey visual class or add to an existing library and then add children components into a DataOdyssey visual class. Initially the list of classes will contain the base WPF classes. Select class from the list and click on “create”.
The class is inherited, and the children class appears in the design area.
The assembly should be added as a reference in the VS project, or can be a separate project in the current solution.
Click “Add Assembly” to show a list of available assemblies.
Select the desired assembly and click “Add.” The assembly will appear in the class trees, and will display a list of its constituent WPF classes. Select class and click “Create.”
The class will be inherited, and its children class will appear in the design area.
Click "Inherit DTO Visual class..." to call the list of recent DataOdyssey Visual class libraries.
The library tree contains the DataOdyssey visual classes.
If the library is not present in this list, click “Class Library.” The “select file” dialog will appear. The DataOdyssey visual class library files have the extension “dtovs.” Select the library created earlier.
Note the parent class information displayed in the Properties Panel (the red box, below) and the structure of the inherited class available for editing (the magenta box, below)
The class name is the same as that of the root element and is shown at the top of the class panel, the name of the first element of the tree and in the Properties Panel when the root element in the tree is selected.
If a class name is present it is disable; click the button to the left of it to change the name.
As mentioned earlier, the DataOdyssey visual designer does not fully implement Visual Studio document behavior. To save changes, you have to click the “Save” button that appears below the design area.
The first time you save a newly created class to a new library, the designer will provide two dialogs, one for the class library file name, and one for the class library namespace.
Note that DataOdyssey visual class libraries have a “dtovx” extension.
Each time you save changes, the designer saves a generated C# file with the extension “dtovs.cs”. The file is associated with the library file in the project (nested file).
To open a dtovx file in the DataOdyssey visual designer, click on it (or doubleclick) in the solution explorer. If the dtovx library contains several classes, the designer will open a class selection dialog window. If the class is already opened in the designer, Visual Studio will navigate to the window where the class is opened.
If Visual Studio opens the dtovx file as JSON, the DataOdyssey visual extension is not loaded. Wait while Visual Studio loads the extension then try again
If the width and/or height of the root control are not set, specify them below the class name. The default is 500 X 500.
The class library namespace is shown at the bottom of the control panel to the right of the “Library Namespace” button. Clicking on the button displays the dialog with the value of the current namespace, allowing you to change it.
Select parent control in the Controls Tree and click on the “Add” button above the tree. Then, select one of the base WPF classes.
Select a parent control in the Controls Tree and click the “Add” button above the tree. The list of available WPF classes is grouped by “Common” and by assembly. Expand the desired assembly, then select a class.
If a desired assembly is not present in the list, click on “Add Assembly.” A dialog containing the assemblies referenced in the project, as well as projects included in the solution, will be displayed.
Click on the “Add” button above the Controls Tree, then click on “Add DTO Visual Class.” A list of available libraries will appear. Expand as needed and select the DataOdyssey visual class created earlier.
If the desired library is not on the list, click on “Class Library” and select the desired "dtovs" file.
There are two ways to create a new class (or add control), based on an existing DataOdyssey Visual class:
Inheriting from a DataOdyssey visual class has several advantages:
So, if the class from which you’re inheriting is in the same assembly, or if you need to make changes in the layout of the derived class, you should inherit from the DataOdyssey visual class.
Inheriting from an assembly also has some advantages:
Assembly inheritance doesn’t mean that you won’t be able to change the design properties of controls in the inherited class, or move controls within the tree of the new class. It just means that the designer has to extract the needed control form the inherited class and before changing properties or moving control.
Controls that have been added always appear in a bold font in the Controls Tree. The names of subcontrol members have a font weight of normal, and can’t be changed.
Added and inherited controls also have different icons.
The DataOdyssey visual designer window consists of a design area on the left and a control panel on the right.
At the top of the Control Panel you will find textbox controls for the class name and design area height and width, “Add” and “Delete” buttons, and textbox for the name of the CreateLayout method and a button to change it.
A “Tree of Controls” representing the visual tree (not class members) occupies the top half of the control panel.
When you click on a tree, the visual path of the selected tree control appears below it.
The Properties Paneloccupies the bottom half of the Control Panel. This is where the designer displays properties of the selected tree control and where they can be changed.
At the bottom of the Control Panel there is an “Add Property” button that allows you to add other properties of the control. You can include initialization code using the “C# Init” button, and click the "Library Namespace" button to name the library.
Below the tree you will see the selected control path, properties and additional buttons.
Controls Tree items start with control names. The name of the selected item also shown on the top of Control Properties scrolling area.
If you change the control name, the DataOdyssey designer will retain links to this control and class (if the class name also change) in all dtovx files (links are based on GUID keys).
However, you should reopen and save all dtovx files that refer to the changed control name, in order to regenerate c# code. Broken references will be highlighted by Visual Studio. Any assemblies that reference the control with the changed name will be broken, and you will have to fix any lost links shown as errors by the compiler. Just keep this in mind when changing control and class names.
Controls tree can be very complex; you can search for a control by its name. There is an option to search by “starts with…” or “contains…”.
All properties with inherited values are by default disabled; click on the checkbox to the left of a property to override its default values. To reset the property to its default value, uncheck the checkbox.
Common properties are shown in the scrolling area of the Control Properties panel. Usually these are frequently used properties inherited from the corresponding base class, like Header for an ItemsControl, Text for a TextBox or TextBlock, etc.
Some of the main properties are not styled as they typically are in WPF. For example, Visibility (Collapsed) is represented as "Invisible."
Some property types (e.g. Thickness, Brush, etc) have unique formats:
Some values are represented using 4 integers, for example, Margin:
Brushes are specified as a background or border color, with a “…” button to call the brushes dialog:
The first brush editing dialog lets you select the type of brush.
If no gradient is selected, you just select a color:
If you select a linear gradient, you will need to define a set of colors and rotate the start and end points.
For a radial gradient, you will need to move the center.
In the case of an image brush, you need to select an image from a file by providing its full path.
If the image is located inside an assembly, you will need to provide both the full path of the image and the full path of the assembly in which the image is declared as a resource.
For grid rows and columns, you will need to provide two pairs of properties: One pair for columns, and one for rows. “Additional” rows and columns can be optionally specified.
"Additional rows" and "Additional columns" can be useful when you inherit class and don't need to change existing rows/columns but need additional. Generated code will be more compact.
If additional properties are needed, click “Add property…” then find the property, select it, and click “Ok.” The property will appear in the Control Properties area.
If a property is of a specific type other than primitive, thickness, brush or Enum) and this property should be set at design time, you will have to write some C# code. For example:
A good example of the "C# Init" code is TextBlock inlines.
Another good example of the "C# Init" code is DataGrid columns adding. Telerik RsdGridView is shown on the picture below.
Drag and drop operations are allowed within the Controls Tree of a window. The designer will show available drop options:
You can also right-click to select a control and select Cut (or Copy if the target is another window) and then right click on a destination control and select Paste. This can also be done with inherited subcontrols.
You also can move inherited subcontrols!
To copy controls between windows, right click on a control and select Copy, then click on a target control in another DataOdyssey visual designer window and select Paste. The same dialog will appear.
The same dialog appears
Controls that are inherited from assembliesare represented as a single item in the Controls tree. Contained controls are not initially accessible. If you want to change a contained control, right click on the container control and select "Extract member...".
The designer will show a list of contained controls to extract. Select one and click “Ok.”
Extracted members appear in italics. You can change (override) their properties, or even move them within the Controls Tree.
To delete a control, select it in the Control tree and click on “Delete.” Take care to handle broken links.
The designer generates C# code each time you save a class that has been edited. The generated “partial” code file has an extension of dtovs.cs, so that generated code doesn’t overwrite related source code.
All generated classes are declared as "partial". Proposed that the main behaviour of class will be implemented in another place as "partial" class code.
But there is an opportunity to write code in the .dtovs.cs file between "Add code below" lines.
By default, the designer generates the code to build the visual tree in a separate method called “CreateLayout” (i.e. not in “Init”). If an ancestor already has a CreateLayout virtual method, the designer will determine the return type and override it.
If you want to force the designer to generate this code, don’t call it CreateLayout; give it a different name.
You can change the CreateLayout method name only if the class is not inherited from another DataOdyssey visual class.
To add a new class to an existing library, right click on the dtovx file in Solution Explorer and select “Add DataOdyssey Visual Class,” and then follow the dialogs that follow as done when you create a new class (described above.)
We recommend keeping all related dtovx files together in one place within a Visual Studio project - as nested of "dtovx" file. Related methods should be located in .cs files with the same library names.
If you’re using MVVM, use the naming convention “*VM.cs”
Right click .dtovx class in the solution explorer and select "Add c# file for partial class".
Enter the name of a new C# file or click "Existing ..." button to add existing file of any type.
To navigate to a control in the C# code, double-click on the control in the Controls Tree, or click “View Code.” The designer will navigate to the control’s definition in the C# code file.
"View Code ..." button finds all references to the selected control, shows in the list. Just select and click "Open" to navigate in the code.
To prevent sharing conflicts, the DataOdyssey visual designer does not load assemblies directly. Visual Studio should be able to rebuild them at any time. The designer always makes copies of all DLLs in the Obj subfolder. Before loading, the designer always checks the last update time of each DLL and copies the DLL to a temporary file is the existing temp file is not current.
Errors in controls can cause Visual Studio to crash while they are being loaded. The designer tries to handle all possible errors, but it may not catch them all. So, use error handlers in your code.
Raising events is an aspect of code that is particularly prone to errors. Designer area intercepts events:
The designer intercepts certain events and marks them as “Handled=true;”:
However, other events can also cause crashes. Good error handling is recommended.
To delete a class from a library, open the dialog with the libraries list. Click the “Add” button, then the “Inherit DTO Visual Class” button. A button to remove each class from the library will appear to the right of each class name.