You are viewing an old version of this page. View the current version.
Compare with Current
View Page History
Version 1
Next »
A custom WPF control is a WPF UserControl that you compile into a DLL. On a display, the WPF Control element loads that DLL, lists the control's public properties, and binds them to tags.
This page covers building the control: the project, the classes, the rules the Designer applies, and a template you can download and adapt.
Download the template
Download WPFControl-TCustom.zip. It contains a complete, minimal control named TCustom: it reads two values, shows each one in a text box, and shows both joined as A_B. Build it, place it on a display, then change it into your own control.
File | Role |
|---|
TCustom.cs
| The control. The only public class in the assembly. Properties, visual construction, tag linking, save and clipboard support. |
TCustomControl.xaml / .xaml.cs
| The visual. Internal, created by TCustom. |
TCustomConfig.xaml / .xaml.cs
| A configuration dialog. Internal, returned by TCustom.GetConfigControl(). Not used by the WPF Control element (see The IPortableControl members). |
TCustom.png
| Icon, embedded as a resource. |
TCustom.csproj / TCustom.sln
| The project. References the FrameworX assemblies from your installation and does not include them. |
Validated with FrameworX 10.1.5.
Requirements
Windows and Visual Studio with the .NET desktop development workload.
A .NET Framework 4.8 class library, the same target as the FrameworX built-in WPF controls.
A FrameworX installation. The project references T.Library.dll, T.Library.Wpf.dll, T.Toolkit.dll and T.Toolkit.Wpf.dll from the installation's fx-10 folder, by default C:\Program Files\Tatsoft\FrameworX\fx-10\.
The control runs only on WPF clients: Windows, RichClient and SmartClient.
The display must use the WPF engine. The WPF Control element is not available on Portable displays.
Rules
Follow these rules for every custom WPF control. Breaking one does not always produce an error: the control can be missing from a list, show the wrong class, or render blank.
The control class is public. It must be public, not abstract, derive from System.Windows.Controls.UserControl, and have a public parameterless constructor. Only such types are listed in Select Component.
Every other UserControl in the assembly is internal. The visual, the configuration dialog and any helper control must be internal, or they are also listed in Select Component and a user can pick the wrong one. For a XAML UserControl, set x:ClassModifier="internal" on the root element and declare the code-behind as internal partial class.
The assembly name equals the control's full type name. Namespace plus class: the template's control T.Portable.Controls.TCustom compiles to T.Portable.Controls.TCustom.dll. The WPF Control settings reject a DLL whose file name does not match the selected control.
Every bindable value is a public property that raises PropertyChanged. Implement INotifyPropertyChanged. A property computed from others (the template's ConcatenatedText) must also be notified when one of its inputs changes, or the display does not refresh it.
The visual is built in the constructor, once. Do not rely on a later lifecycle call to create it, and never add a child element that already has a parent. Either mistake leaves the control blank on the display.
FrameworX assemblies are referenced, not copied. Set Private to False (Copy Local off) on every FrameworX reference. FrameworX loads its own copies at runtime.
Close the Designer and any running client before rebuilding. They keep the DLL loaded, and the build cannot overwrite it.
Build your own control from the template
1. Open and build the template
Open TCustom.sln and build. The output is bin\Release\T.Portable.Controls.TCustom.dll (or bin\Debug\). If FrameworX is not installed in C:\Program Files\Tatsoft\FrameworX, change FrameworXPath in TCustom.csproj to your installation's fx-10 folder, keeping the trailing backslash.
<!-- The assembly name MUST equal the full type name of the control class (namespace + class). -->
<AssemblyName>T.Portable.Controls.TCustom</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<!-- Folder that holds the FrameworX reference assemblies. Override on the command line with
/p:FrameworXPath=<your folder>\ if FrameworX is installed somewhere else. Keep the trailing backslash. -->
<FrameworXPath Condition=" '$(FrameworXPath)' == '' ">C:\Program Files\Tatsoft\FrameworX\fx-10\</FrameworXPath>
<Reference Include="T.Library">
<HintPath>$(FrameworXPath)T.Library.dll</HintPath>
<Private>False</Private>
</Reference>
2. Rename it
Choose your namespace and class name, then change every place below. Keep rule 3: AssemblyName must equal namespace plus class.
File | What to change |
|---|
TCustom.csproj
| AssemblyName and RootNamespace.
|
TCustom.cs
| The namespace, the class name, the constructor name, and the assembly and file name in the pack:// URI inside GetIconImage(). |
TCustomControl.xaml, TCustomConfig.xaml
| x:Class, the clr-namespace, and the control type in {x:Type local:TCustom}.
|
TCustomControl.xaml.cs, TCustomConfig.xaml.cs
| The namespace, the class names, and every reference to TCustom. |
3. Add your properties
Each value the display can bind to is a public property. The setter returns early when the value is unchanged, stores it, and raises PropertyChanged for the property itself and for every property computed from it.
private string _firstTextValue = string.Empty;
/// <summary>
/// Resolved runtime value of the first token/object.
/// </summary>
public string FirstTextValueStr
{
get => _firstTextValue;
set
{
if (_firstTextValue == value)
return;
_firstTextValue = value;
_firstTextObjectRef?.SetValue(value);
OnPropertyChanged();
OnPropertyChanged(nameof(ConcatenatedText));
}
}
/// <summary>
/// Combined text shown by the control.
/// </summary>
public string ConcatenatedText => $"{FirstTextValueStr}_{SecondTextValueStr}";
The line _firstTextObjectRef?.SetValue(value) belongs to the template's own tag linking (see The IPortableControl members). It does nothing when that link is not set, so the property works the same with the WPF Control element.
4. Design the visual
The visual is a separate internal UserControl. Its bindings name the control class as their source through RelativeSource, so they reach your properties whatever the DataContext is. UpdateSourceTrigger=PropertyChanged pushes each keystroke to the property instead of waiting for the text box to lose focus.
<UserControl x:Class="T.Portable.Controls.TCustomControl"
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:T.Portable.Controls"
x:ClassModifier="internal"
mc:Ignorable="d">
<StackPanel Margin="10" Width="360">
<TextBlock Margin="0,0,0,6" Text="CustomControl (render)"/>
<TextBlock FontWeight="Bold"><Run Text="StringA"/><Run Text=":"/></TextBlock>
<TextBox Text="{Binding Path=FirstTextValueStr, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type local:TCustom}}}"/>
<TextBlock FontWeight="Bold" Text="StringB:"/>
<TextBox Text="{Binding Path=SecondTextValueStr, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type local:TCustom}}}"/>
<Border Padding="8" BorderBrush="Gray" BorderThickness="1" CornerRadius="4">
<StackPanel>
<TextBlock FontWeight="Bold" Text="StringA_StringB:"/>
<TextBlock Margin="0,6,0,0"
Text="{Binding Path=ConcatenatedText, RelativeSource={RelativeSource AncestorType={x:Type local:TCustom}}}"
Background="White"
/>
</StackPanel>
</Border>
</StackPanel>
</UserControl>
The control class builds the visual in its constructor through one method that is safe to call any number of times. It creates the grid only when there is none, and adds the child only when it has not been added. Loaded and StartRuntime() call the same method.
public TCustom()
{
MinWidth = 10;
MinHeight = 10;
// Build the visual up front so the control is never empty,
// whichever lifecycle hook the host calls first.
EnsureVisual();
Loaded += OnLoaded;
}
private void EnsureVisual()
{
if (_mainGrid == null)
{
_mainGrid = new Grid();
Content = _mainGrid;
}
if (_control == null)
{
_control = new TCustomControl(this);
_mainGrid.Children.Add(_control);
}
_control.SetDataContextToParent();
_mainGrid.Background = Background;
}
5. Place it on a display
Close the Designer if it has the previous build loaded (rule 7), build, and then place the control with the WPF Control element as described in WPF Control Reference: select your DLL, then pick your control class in Select Component. Only the public control class is listed there.
Double-click the control on the display to open its settings. The Properties table lists the control's public properties. For each value, enter the tag in ObjectLink and choose the Binding mode. For the template:
ControlProperty | Binding | ObjectLink |
|---|
FirstTextValueStr
| TwoWay | Tag.Integer1
|
SecondTextValueStr
| TwoWay | Tag.Integer2
|
ConcatenatedText
| OneWay | Leave empty. It is read-only and is shown by the control. |
With these links, the control shows each tag's value and the two values joined. The binding modes are described in WPF Control Reference.
The IPortableControl members
The WPF Control element only needs a public UserControl. The template also implements the IPortableControl interface (namespace T.Toolkit), which carries the members FrameworX uses for controls registered as extension controls. On the WPF Control element, tags are bound through the Properties table and the dialog returned by GetConfigControl() is not opened. You can keep these members as they are.
Member | What the template does |
|---|
EndInit()
| Calls base.EndInit() and WK.EndInit(this), once. |
StartRuntime()
| Builds the visual if needed, then parses FirstTokenLinkStr and SecondTokenLinkStr into ObjectReference objects and subscribes to their changes. |
Dispose()
| Releases the ObjectReference subscriptions. |
GetTokens, ApplyTokens
| Expose the link expressions to symbol token replacement. |
GetLabels, ResolveLabels
| Resolve labels in the link expressions. |
GetStrings, ApplyStrings
| Localization hooks. Empty in the template. |
OnStartClipboardCopy, OnFinishClipboardCopy, OnFinishClipboardPaste
| Keep the link expressions valid through copy and paste. |
OnSave(int displayId, object el)
| Saves the link expressions with the display. |
GetConfigControl()
| Returns the configuration dialog TCustomConfig, whose field editors use the EditInfo exposed by NumberOrObjectField. |
GetIconImage()
| Static method, not part of the interface. Returns the icon embedded as TCustom.png. |
Troubleshooting
Symptom | Cause | Fix |
|---|
Select Component lists more classes than your control. | Other UserControls in the assembly are public. | Make them internal (rule 2). |
Warning: "DLL filename must be same of the control". | The selected class's full name does not match the DLL file name. | Select the control class, or set AssemblyName to namespace plus class (rule 3). |
The control is blank on the display. | The visual is created only in a later lifecycle call, or a child element is added a second time. | Build the visual in the constructor, once (rule 5, step 4). |
A value does not refresh on the display. | The property, or a property it feeds, does not raise PropertyChanged. | Raise it in the setter for the property and for every computed property (rule 4). |
Build fails: "The process cannot access the file ... because it is being used by another process". | The Designer or a running client has the DLL loaded. | Close them and build again (rule 7). |
WPF Control is not offered in the Viewer group. | The display uses the Portable engine. | Place the control on a display whose engine is WPF. |
Your control's own configuration dialog does not open. | The WPF Control element uses its own Properties table. | Expected. Bind the properties in that table (step 6). |
Complete source
The full template, file by file. The same files are in WPFControl-TCustom.zip.
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using T.Library;
using T.Toolkit;
using T.Toolkit.Wpf;
namespace T.Portable.Controls
{
/// <summary>
/// Example Portable WPF control.
/// Demonstrates:
/// - PortableControl lifecycle
/// - Token / label handling
/// - ObjectReference binding
/// - Design-time vs runtime rendering
///
/// This class should be used as a reference implementation
/// for new WPF Portable Controls.
/// </summary>
public class TCustom : UserControl, INotifyPropertyChanged, IPortableControl
{
#region Linked Object References
private ObjectReference _firstTextObjectRef;
private ObjectReference _secondTextObjectRef;
#endregion
#region Token / Link Properties
private string _firstTokenLink = string.Empty;
/// <summary>
/// EditInfo consumed by the FieldEditor controls in TCustomConfig.
/// Must be a real EditInfo, not a bare object, or the editor renders
/// without the object/tag browse behaviour.
/// </summary>
public EditInfo NumberOrObjectField => EditInfo.FactoryNumberOrObject;
/// <summary>
/// Token or object link expression for the first text value.
/// </summary>
public string FirstTokenLinkStr
{
get => _firstTokenLink;
set
{
if (_firstTokenLink == value)
return;
_firstTokenLink = value;
OnPropertyChanged();
}
}
private string _secondTokenLink = string.Empty;
/// <summary>
/// Token or object link expression for the second text value.
/// </summary>
public string SecondTokenLinkStr
{
get => _secondTokenLink;
set
{
if (_secondTokenLink == value)
return;
_secondTokenLink = value;
OnPropertyChanged();
}
}
#endregion
#region Runtime Values
private string _firstTextValue = string.Empty;
/// <summary>
/// Resolved runtime value of the first token/object.
/// </summary>
public string FirstTextValueStr
{
get => _firstTextValue;
set
{
if (_firstTextValue == value)
return;
_firstTextValue = value;
_firstTextObjectRef?.SetValue(value);
OnPropertyChanged();
OnPropertyChanged(nameof(ConcatenatedText));
}
}
private string _secondTextValue = string.Empty;
/// <summary>
/// Resolved runtime value of the second token/object.
/// </summary>
public string SecondTextValueStr
{
get => _secondTextValue;
set
{
if (_secondTextValue == value)
return;
_secondTextValue = value;
_secondTextObjectRef?.SetValue(value);
OnPropertyChanged();
OnPropertyChanged(nameof(ConcatenatedText));
}
}
/// <summary>
/// Combined text shown by the control.
/// </summary>
public string ConcatenatedText => $"{FirstTextValueStr}_{SecondTextValueStr}";
#endregion
#region Visual Infrastructure
private Grid _mainGrid;
private TCustomControl _control;
private bool _endInitAlreadyRun;
#endregion
#region Constructor
public TCustom()
{
MinWidth = 10;
MinHeight = 10;
// Build the visual up front so the control is never empty,
// whichever lifecycle hook the host calls first.
EnsureVisual();
Loaded += OnLoaded;
}
#endregion
#region Initialization Lifecycle
/// <summary>
/// Final step of WPF initialization.
///
/// IMPORTANT:
/// - base.EndInit() MUST be called
/// - WK.EndInit registers the DisplayHandler
/// - Must run only once
/// </summary>
public override void EndInit()
{
if (_endInitAlreadyRun)
return;
_endInitAlreadyRun = true;
#if !OPENSILVER
// Required by the WPF UserControl initialization contract.
base.EndInit();
#endif
WK.EndInit(this);
if (WK.IsDesignMode)
return;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
EnsureVisual();
}
#endregion
#region Visual Construction
/// <summary>
/// Builds the visual tree once. Safe to call any number of times and
/// from any hook (constructor, Loaded, StartRuntime).
///
/// IMPORTANT: never recreate the Grid or re-add the child. Adding a
/// control that still has a parent throws InvalidOperationException,
/// which leaves the control rendering blank.
/// </summary>
private void EnsureVisual()
{
if (_mainGrid == null)
{
_mainGrid = new Grid();
Content = _mainGrid;
}
if (_control == null)
{
_control = new TCustomControl(this);
_mainGrid.Children.Add(_control);
}
_control.SetDataContextToParent();
_mainGrid.Background = Background;
}
#endregion
#region Runtime Logic
/// <summary>
/// Handles updates coming from ObjectReference bindings.
/// Triggered whenever linked objects change.
/// </summary>
private async Task HandleTagEvent()
{
FirstTextValueStr = TConvert.ToString(_firstTextObjectRef?.GetValue());
SecondTextValueStr = TConvert.ToString(_secondTextObjectRef?.GetValue());
}
public void StartRuntime()
{
EnsureVisual();
try
{
if (WK.IsDesignMode)
return;
// Parse and subscribe references
_firstTextObjectRef =
ObjectReference.ParseExpression(FirstTokenLinkStr?.Trim(), null);
if (!WK.IsUpdatingReport && _firstTextObjectRef != null)
_firstTextObjectRef.RegisterEvent(HandleTagEvent);
_secondTextObjectRef =
ObjectReference.ParseExpression(SecondTokenLinkStr?.Trim(), null);
if (!WK.IsUpdatingReport && _secondTextObjectRef != null)
_secondTextObjectRef.RegisterEvent(HandleTagEvent);
// Initial refresh AFTER refs exist
_ = HandleTagEvent();
}
catch (Exception ex)
{
TException.Log(ex);
}
}
#endregion
#region Toolbox Icon
/// <summary>
/// Returns the toolbox icon for this control.
/// </summary>
public static byte[] GetIconImage()
{
const string uri =
"pack://application:,,,/T.Portable.Controls.TCustom;component/TCustom.png";
return WK.GetImageFromUri(uri);
}
#endregion
#region IPortableControl – Localization / Tokens / Clipboard
public void GetStrings(object replaceControl) { }
public void ApplyStrings(object replaceControl) { }
public void GetTokens(object symbol)
{
WK.GetTokens(symbol, _firstTokenLink, eFieldTypeTk.Object);
WK.GetTokens(symbol, _secondTokenLink, eFieldTypeTk.Object);
}
public void ApplyTokens(object symbol)
{
_firstTokenLink = WK.ApplyTokens(symbol, _firstTokenLink, eFieldTypeTk.Object);
_secondTokenLink = WK.ApplyTokens(symbol, _secondTokenLink, eFieldTypeTk.Object);
}
public void GetLabels(object symbol)
{
WK.GetLabel(symbol, _firstTokenLink, eFieldTypeTk.Object);
WK.GetLabel(symbol, _secondTokenLink, eFieldTypeTk.Object);
}
public void ResolveLabels(object symbol)
{
WK.ResolveLabel(symbol, ref _firstTokenLink, eFieldTypeTk.Object);
WK.ResolveLabel(symbol, ref _secondTokenLink, eFieldTypeTk.Object);
}
public void OnStartClipboardCopy()
{
_firstTokenLink = WK.OnStartClipboardCopy(_firstTokenLink);
_secondTokenLink = WK.OnStartClipboardCopy(_secondTokenLink);
}
public void OnFinishClipboardCopy()
{
_firstTokenLink = WK.OnFinishClipboardCopy(_firstTokenLink);
_secondTokenLink = WK.OnFinishClipboardCopy(_secondTokenLink);
}
public void OnFinishClipboardPaste()
{
_firstTokenLink = WK.OnFinishClipboardPaste(_firstTokenLink);
_secondTokenLink = WK.OnFinishClipboardPaste(_secondTokenLink);
}
public void OnSave(int displayId, object el)
{
OnStartClipboardCopy();
OnFinishClipboardCopy();
WK.SaveHelper.SaveStart(displayId, el);
WK.SaveHelper.SaveLink(FirstTokenLinkStr, nameof(FirstTokenLinkStr));
WK.SaveHelper.SaveLink(SecondTokenLinkStr, nameof(SecondTokenLinkStr));
}
#endregion
#region Cleanup
public void Dispose()
{
WK.Dispose(ref _firstTextObjectRef);
WK.Dispose(ref _secondTextObjectRef);
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
=>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
/// <summary>
/// Returns an instance of the configuration UI for this control.
/// Called by the framework when the user opens the control's configuration dialog.
/// </summary>
/// <returns></returns>
public IControlConfig GetConfigControl()
{
return new TCustomConfig();
}
#endregion
}
}
<UserControl x:Class="T.Portable.Controls.TCustomControl"
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:T.Portable.Controls"
x:ClassModifier="internal"
mc:Ignorable="d">
<StackPanel Margin="10" Width="360">
<TextBlock Margin="0,0,0,6" Text="CustomControl (render)"/>
<TextBlock FontWeight="Bold"><Run Text="StringA"/><Run Text=":"/></TextBlock>
<TextBox Text="{Binding Path=FirstTextValueStr, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type local:TCustom}}}"/>
<TextBlock FontWeight="Bold" Text="StringB:"/>
<TextBox Text="{Binding Path=SecondTextValueStr, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type local:TCustom}}}"/>
<Border Padding="8" BorderBrush="Gray" BorderThickness="1" CornerRadius="4">
<StackPanel>
<TextBlock FontWeight="Bold" Text="StringA_StringB:"/>
<TextBlock Margin="0,6,0,0"
Text="{Binding Path=ConcatenatedText, RelativeSource={RelativeSource AncestorType={x:Type local:TCustom}}}"
Background="White"
/>
</StackPanel>
</Border>
</StackPanel>
</UserControl>
using System.Windows.Controls;
namespace T.Portable.Controls
{
/// <summary>
/// Visual layer for <see cref="TCustom"/>.
/// This class must contain only UI logic and no runtime/business rules.
///
/// Kept internal (with x:ClassModifier="internal" on the XAML root) so it
/// does not show up in the Designer's Select Component list. It is only
/// ever constructed by TCustom itself.
/// </summary>
[System.Reflection.Obfuscation(Feature = "renaming", ApplyToMembers = true)]
internal partial class TCustomControl : UserControl
{
private readonly TCustom _parent;
public TCustomControl()
{
InitializeComponent();
}
public TCustomControl(TCustom parent)
{
InitializeComponent();
_parent = parent;
SetDataContextToParent();
}
/// <summary>
/// Sets the DataContext to the owning PortableControl.
/// Required to keep bindings consistent across design-time and runtime.
/// </summary>
public void SetDataContextToParent()
{
if (_parent != null)
DataContext = _parent;
}
}
}
<UserControl x:Class="T.Portable.Controls.TCustomConfig"
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:ttw="clr-namespace:T.Toolkit.Wpf;assembly=T.Toolkit.Wpf"
x:ClassModifier="internal"
mc:Ignorable="d">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="140"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Margin="0,0,8,8" VerticalAlignment="Center"
Text="StringA:"/>
<!-- FieldEditor (must have Text) -->
<ttw:FieldEditor Grid.Row="0" Grid.Column="1" Margin="0,0,0,8"
Text="{Binding Path=FirstTokenLinkStr, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
EditInfo="{Binding Path=NumberOrObjectField}"
VerticalAlignment="Center"
HorizontalAlignment="Stretch" />
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0,0,8,0" VerticalAlignment="Center"
Text="StringB:"/>
<ttw:FieldEditor Grid.Row="1" Grid.Column="1"
Text="{Binding Path=SecondTokenLinkStr, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
EditInfo="{Binding Path=NumberOrObjectField}"
VerticalAlignment="Center"
HorizontalAlignment="Stretch" />
</Grid>
</UserControl>
using System.Windows.Controls;
using T.Toolkit;
namespace T.Portable.Controls
{
/// <summary>
/// Configuration UI for <see cref="TCustom"/>.
/// Loaded by the framework when the user edits control properties.
/// </summary>
internal partial class TCustomConfig : UserControl, IControlConfig
{
private TCustom _control;
public TCustomConfig()
{
InitializeComponent();
}
/// <summary>
/// Loads the target control into the configuration UI.
/// </summary>
public void LoadControl(object control)
{
_control = control as TCustom;
DataContext = _control;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{80A4DE71-2304-4AD4-96DA-0407245FAEBC}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>T.Portable.Controls</RootNamespace>
<!-- The assembly name MUST equal the full type name of the control class (namespace + class). -->
<AssemblyName>T.Portable.Controls.TCustom</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<!-- Folder that holds the FrameworX reference assemblies. Override on the command line with
/p:FrameworXPath=<your folder>\ if FrameworX is installed somewhere else. Keep the trailing backslash. -->
<FrameworXPath Condition=" '$(FrameworXPath)' == '' ">C:\Program Files\Tatsoft\FrameworX\fx-10\</FrameworXPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<!-- Taken from the FrameworX folder so it matches the version T.Library was built against. -->
<Reference Include="Microsoft.CSharp">
<HintPath>$(FrameworXPath)Microsoft.CSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Xaml" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
<!-- FrameworX assemblies. Private=False: they are referenced, never copied to the output.
FrameworX already loads its own copies at runtime. -->
<Reference Include="T.Library">
<HintPath>$(FrameworXPath)T.Library.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="T.Library.Wpf">
<HintPath>$(FrameworXPath)T.Library.Wpf.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="T.Toolkit">
<HintPath>$(FrameworXPath)T.Toolkit.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="T.Toolkit.Wpf">
<HintPath>$(FrameworXPath)T.Toolkit.Wpf.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="TCustom.cs" />
<Compile Include="TCustomConfig.xaml.cs">
<DependentUpon>TCustomConfig.xaml</DependentUpon>
</Compile>
<Compile Include="TCustomControl.xaml.cs">
<DependentUpon>TCustomControl.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Page Include="TCustomConfig.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="TCustomControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Resource Include="TCustom.png" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
In this section...
The root page @parent could not be found in space FrameworX 10.1.