メインコンテンツまでスキップ

ComponentOne (C1.WPF) 使用指南

本文件說明 GST 專案中 ComponentOne WPF 元件的使用模式、效能最佳化與主題整合。 適用於 ProcessVision 和 Jope-PLC-Monitor。


整合流程總覽

從新專案第一次引入 C1.WPF 到畫面能看到控件,要經過 安裝 → 授權 → 引入 ResourceDictionary → XAML 使用 → Runtime 驗證 → 渲染 六個步驟。下圖同時把「授權驗證失敗 (license 過期 / 未涵蓋部署環境)」的 fallback 路徑畫出來:

各步驟對照:

步驟動作常見陷阱
1dotnet add package C1.WPF.Core 等主套件(見 §1 清單)版本要跟 C1.WPF.Core 一致,不可混搭不同主版本
2加入 licenses.licx 並讓公司授權檔參與 build(LicenseCompilerNuGet 包自帶 evaluation watermark;正式部署必須靠 licenses.licx 與實際授權檔完成 compile-time license embedding
3App.xamlMergedDictionaries 引入 C1.WPF.Themes.Material.xaml 或 Dark 變體,並在頁面加 xmlns:c1="http://schemas.componentone.com/winfx/2006/xaml"多個 MergedDictionaries 順序會影響樣式覆寫
4直接寫 <c1:FlexChart ...> / <c1:FlexGrid ...> 等控件複雜控件(如 FlexChart Series)建議在 Code-behind 動態建構(見 §2.2)
5dotnet run / 部署到目標機台Release build 不會印授權錯誤到 Console,必須看 Output 或捕捉 LicenseException
6aC1License 驗證通過,控件正常 render
6b → Fallbacklicense 缺失或過期時,控件會 render 成帶浮水印 / 彈窗 / 直接 throw LicenseExceptionFallback 路徑不會自動切到開源替代,必須人為決策

讀圖重點:

  • Fallback 回到步驟 5 而非步驟 1:只需更新 license 檔或呼叫 C1License.SetLicenseKey(key) 後重新啟動 Runtime;不需要重 install NuGet 或改 XAML。
  • 授權驗證發生在 Runtime,不在 Build 時:build 成功不代表部署環境授權有效。部署時必須實機跑一次驗證。
  • 替換為開源替代 (LiveCharts) 是最後手段:會改動 XAML 與 Code-behind,屬於架構變更,不是純運維修復。

1. 使用的元件

套件版本用途
C1.WPF.Core8.0.20242.966核心元件
C1.WPF.Chart8.0.20242.966FlexChart 圖表
C1.WPF.Grid8.0.20242.966FlexGrid 網格
C1.WPF.Input8.0.20242.966輸入控件
C1.WPF.DateTimeEditors8.0.20242.966日期時間選擇器
C1.WPF.Gauge8.0.20242.966儀表盤
C1.WPF.Themes.Material8.0.20242.966Material 主題
C1.WPF.Themes.MaterialDark8.0.20242.966Material Dark 主題

1.1 控件分類

上表按 NuGet 套件列出;下圖則按用途分類,幫助選擇控件:

  • 資料呈現類(Chart / Grid / Gauge)是 GST 專案的主力使用對象;其中 FlexChart 在 §2 有完整使用範例。
  • 輸入類(Input / DateTimeEditors)通常搭配 FlexGrid cell editor 使用,或放在設定畫面。
  • 主題類 兩者互斥,同時只掛一個;切換時直接替換 App.xaml 中的 ResourceDictionary Source。
  • GST 專案目前未使用 ToolBar / Ribbon / DocViewer / RichTextBox 等套件,因此未列入。

2. FlexChart 即時圖表

2.1 XAML 定義

xmlns:c1="http://schemas.componentone.com/winfx/2006/xaml"

<c1:FlexChart x:Name="dataChart"
ChartType="Line"
LegendPosition="Bottom"
ToolTipContent="{}{seriesName}&#x0a;Time: {x:HH:mm:ss}&#x0a;Value: {y:F2}">
<c1:FlexChart.AxisX>
<c1:Axis Title="Time" Format="HH:mm:ss" LabelAngle="-45" MajorGrid="True">
<c1:Axis.MajorGridStyle>
<c1:ChartStyle Stroke="#3A3A3A" />
</c1:Axis.MajorGridStyle>
</c1:Axis>
</c1:FlexChart.AxisX>
<c1:FlexChart.AxisY>
<c1:Axis Title="Value" MajorGrid="True">
<c1:Axis.MajorGridStyle>
<c1:ChartStyle Stroke="#3A3A3A" />
</c1:Axis.MajorGridStyle>
</c1:Axis>
</c1:FlexChart.AxisY>
</c1:FlexChart>

2.2 程式碼建構 Series

private void BuildChartSeries()
{
dataChart.BeginUpdate(); // 暫停渲染
try
{
dataChart.Series.Clear();

var colors = new[]
{
Color.FromRgb(0xFF, 0x57, 0x22), // Deep Orange
Color.FromRgb(0x3F, 0x51, 0xB5), // Indigo
Color.FromRgb(0x4C, 0xAF, 0x50), // Green
Color.FromRgb(0x9C, 0x27, 0xB0), // Purple
Color.FromRgb(0x00, 0xBC, 0xD4), // Cyan
Color.FromRgb(0xFF, 0xC1, 0x07), // Amber
Color.FromRgb(0xE9, 0x1E, 0x63), // Pink
Color.FromRgb(0x60, 0x7D, 0x8B) // Blue Grey
};

int colorIndex = 0;
foreach (var dataSeries in _viewModel.DataSeries)
{
if (!dataSeries.IsVisible || dataSeries.DataPoints.Count == 0)
continue;

var brush = new SolidColorBrush(colors[colorIndex % colors.Length]);
brush.Freeze(); // 記憶體最佳化

var series = new Series
{
SeriesName = dataSeries.DisplayName,
ItemsSource = dataSeries.DataPoints,
Binding = "Value",
BindingX = "Timestamp",
Style = new ChartStyle
{
Stroke = brush,
StrokeThickness = 2
}
};

dataChart.Series.Add(series);
colorIndex++;
}
}
finally
{
dataChart.EndUpdate(); // 恢復渲染
}
}

2.3 Data Model

public class ChartDataPoint
{
public DateTime Timestamp { get; set; }
public double Value { get; set; }
}

Series 的 Binding 對應 Value 屬性,BindingX 對應 Timestamp 屬性。

2.4 即時更新(Incremental Refresh)

private void RefreshChartData()
{
dataChart.BeginUpdate();
try
{
var selectedTags = _viewModel.SelectedTags;
for (int i = 0; i < dataChart.Series.Count && i < selectedTags.Count; i++)
{
// 只更新 ItemsSource,不重建 Series
dataChart.Series[i].ItemsSource = selectedTags[i].GetPoints();
}
}
finally
{
dataChart.EndUpdate();
}
}

重點:不要在每次 Polling 時重建 Series,只更新 ItemsSource

3. 效能最佳化

3.1 BeginUpdate / EndUpdate

chart.BeginUpdate();    // 暫停所有渲染
try
{
// 批量操作(加/移 Series、更新 ItemsSource)
}
finally
{
chart.EndUpdate(); // 一次性渲染
}

必須使用。不使用的話,每次 Series.Add()ItemsSource 變更都會觸發重繪,大量數據時會造成 UI 卡頓。

3.2 Brush Freezing

var brush = new SolidColorBrush(color);
brush.Freeze(); // 建立不可變副本,減少記憶體

對重複使用的 Brush 呼叫 Freeze(),避免 WPF 追蹤變更。

3.3 事件驅動更新

// ViewModel 端
_viewModel.DataPointsUpdated += (s, e) => RefreshChartData();

// 不要在 Polling 迴圈中直接操作 Chart
// 透過事件解耦,由 UI 層決定更新時機

3.4 大量數據處理建議

數據量建議
< 1,000 點直接綁定,無需特殊處理
1,000 - 10,000 點使用 BeginUpdate/EndUpdate
> 10,000 點考慮降採樣(downsample)後再綁定

4. 主題整合

4.1 支援的主題

主題類別適用場景
C1ThemeMaterialDark深色工業監控(預設)
C1ThemeMaterial淺色一般辦公
C1ThemeSystem系統跟隨 Windows 設定
C1ThemeOffice365WhiteOffice文件處理

4.2 套用主題

// ThemeHelper.cs
var theme = ThemeFactories[themeName]();
_currentThemeResources = theme.ThemeResources;

if (_currentThemeResources != null)
{
app.Resources.MergedDictionaries.Insert(0, _currentThemeResources);
}

4.3 自訂 Palette

Resources/Themes/
├── DarkPalette.xaml ← 深色主題色板
└── LightPalette.xaml ← 淺色主題色板

圖表的背景色、網格線、文字顏色透過 Palette 統一管理,確保與整體主題一致。

5. ToolTip 格式

ToolTipContent="{}{seriesName}&#x0a;Time: {x:HH:mm:ss}&#x0a;Value: {y:F2}"
語法說明
{seriesName}Series 名稱
{x:format}X 軸值(支援 DateTime 格式)
{y:format}Y 軸值(支援數值格式)
{value:format}{y}
&#x0a;換行(XML escaped)

6. 授權管理

目前兩個專案未顯式呼叫 C1License.SetLicenseKey(),依賴 NuGet 套件的隱式授權機制。

部署時注意:

  • 確認 ComponentOne 授權涵蓋部署環境
  • .NET Reactor 保護不會影響 C1 授權驗證(C1 DLL 不在保護範圍內)

7. 開發規範

7.1 Series 管理

  • 在 Code-behind 建構 Series,不在 XAML 中靜態定義(因為 Series 數量動態變化)
  • 使用預定義色彩陣列 + index 分配,確保視覺一致性
  • 提供 Empty State 處理(無數據時顯示提示文字)

7.2 效能

  • 永遠BeginUpdate/EndUpdate 包裹批量操作
  • Brush.Freeze() 減少 WPF 追蹤開銷
  • Polling 資料透過事件通知 UI,不直接在 Polling 迴圈操作 Chart

7.3 主題

  • 所有 Chart 的網格線、背景色使用 Palette Resource
  • 不硬編碼色彩值(除了 Series 色彩陣列)

8. 涉及專案

專案C1 用途說明
ProcessVisionFlexChart(歷史數據圖表)溫度/壓力趨勢、Data Chart Window
PLC-MonitorFlexChart(即時趨勢)+ FlexGrid多 Tag 即時監控、Alarm Grid

文件版本:v1.0 | 建立日期:2026-03-16 | 對應 Issue:GST-171