WPF构建自动化行业漂亮界面框架实战

WPF自动化界面开发实战
部署运行你感兴趣的模型镜像

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:WPF作为微软.NET平台下的现代化UI框架,凭借其强大的数据绑定、样式模板和可视化组件能力,广泛应用于自动化行业的界面开发。本项目围绕一个基于WPF的DrawerMenuStyle解决方案展开,使用VS2022开发环境,结合DOT6运行环境,展示如何构建一个专业、美观且高效响应的自动化控制界面框架。项目内容涵盖界面布局、多线程处理、样式设计与菜单交互实现,适合希望提升WPF实战能力并应用于工业自动化的开发者学习和拓展。
WPF+自动化行业+漂亮的界面框架

1. WPF技术概述与核心特性

Windows Presentation Foundation(WPF)是微软推出的用于构建现代桌面应用程序的图形界面框架,基于.NET Framework,提供了一套完整的UI开发解决方案。WPF采用矢量图形渲染引擎,支持硬件加速,能够实现丰富的视觉效果与动画交互。

与传统的WinForm相比,WPF具备更高级的UI抽象能力,其核心架构基于XAML(Extensible Application Markup Language),实现了UI与业务逻辑的清晰分离,提升了代码的可维护性与设计协作效率。

本章将从WPF的技术背景出发,深入剖析其核心特性,包括XAML的作用机制、布局系统、样式模板机制等,并探讨其在工业自动化等现代桌面应用中的广泛应用价值。

2. 数据绑定机制详解与应用

WPF的数据绑定机制是其最为强大的特性之一,它使得UI与业务逻辑之间的交互变得极为简洁高效。通过数据绑定,开发者可以轻松实现界面元素与数据源之间的双向同步,无需手动编写大量更新逻辑。本章将深入探讨WPF数据绑定的基础概念、绑定模式、实际应用场景以及高级绑定技巧,帮助读者构建高效、可维护的现代WPF应用,特别是在工业自动化等需要高实时性和稳定性的场景中。

2.1 数据绑定基础

WPF的数据绑定机制基于其MVVM(Model-View-ViewModel)设计模式,允许开发者将UI控件与底层数据对象直接绑定。这种机制不仅提高了代码的可读性和可维护性,还极大地提升了开发效率。

2.1.1 绑定的概念与基本结构

数据绑定的核心在于“绑定”这一动作,即将某个UI元素(如TextBox、Label等)的属性与一个数据源对象的属性建立连接。当数据源属性发生变化时,UI会自动更新;反之,当用户在UI上进行操作时,数据源也会同步更新(取决于绑定模式)。

绑定的基本结构如下:

<TextBlock Text="{Binding Path=Name}" />

在这个例子中:

  • TextBlock 是绑定的目标(Target)。
  • Text 是目标的绑定属性。
  • Binding Path=Name 表示绑定源(Source)中的 Name 属性。

绑定源可以是任意实现了 INotifyPropertyChanged 接口的对象,也可以是一个静态资源、依赖对象或集合。

绑定结构详解

绑定语法支持多种配置选项,包括路径(Path)、源(Source)、模式(Mode)、更新触发器(UpdateSourceTrigger)等。

<TextBox Text="{Binding Path=Temperature, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

在这个例子中:

属性 含义
Path=Temperature 绑定源对象的 Temperature 属性
Mode=TwoWay 数据双向绑定
UpdateSourceTrigger=PropertyChanged 当文本框内容发生变化时立即更新绑定源

说明: INotifyPropertyChanged 接口是实现绑定响应变化的关键。绑定源对象必须实现该接口,当属性值变化时触发 PropertyChanged 事件,通知绑定目标进行更新。

2.1.2 绑定源与绑定目标的关系

绑定源(Source)和绑定目标(Target)之间的关系是数据绑定的核心。绑定源通常是业务逻辑中的数据模型或ViewModel,而绑定目标则是UI元素的属性。

示例代码:绑定源实现
public class SensorData : INotifyPropertyChanged
{
    private double temperature;
    public double Temperature
    {
        get { return temperature; }
        set
        {
            if (temperature != value)
            {
                temperature = value;
                OnPropertyChanged(nameof(Temperature));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
示例代码:绑定目标设置

在XAML中设置绑定源上下文(DataContext):

<Window.DataContext>
    <local:SensorData />
</Window.DataContext>

绑定目标使用绑定源属性:

<TextBox Text="{Binding Path=Temperature, Mode=TwoWay}" />
<TextBlock Text="{Binding Path=Temperature}" />

在这个例子中:

  • SensorData 对象作为绑定源。
  • TextBox TextBlock Text 属性分别绑定到 Temperature
  • TextBox 使用 TwoWay 模式,允许用户修改温度值,而 TextBlock 只读显示。

逻辑分析:当用户在 TextBox 中输入新的温度值时,由于绑定是双向的, SensorData 对象中的 Temperature 属性会被更新。此时, TextBlock 会自动刷新显示新值,因为其绑定的是同一个属性,并且源对象实现了 INotifyPropertyChanged

2.2 绑定模式与更新策略

WPF支持多种绑定模式,开发者可以根据具体需求选择合适的绑定方式,以优化性能并确保数据一致性。

2.2.1 OneWay、TwoWay与OneTime绑定模式

绑定模式决定了数据如何在源与目标之间流动:

模式 描述 适用场景
OneWay 数据从源流向目标,目标不能修改源 只读数据显示
TwoWay 数据双向流动,适用于用户输入和实时反馈 表单编辑、实时数据输入
OneTime 数据绑定仅在初始化时更新一次 静态数据显示
示例代码:不同绑定模式对比
<TextBlock Text="{Binding Path=Temperature, Mode=OneWay}" />
<TextBox Text="{Binding Path=Temperature, Mode=TwoWay}" />
<Label Content="{Binding Path=DeviceId, Mode=OneTime}" />
  • TextBlock 使用 OneWay 模式:只显示数据,不响应用户输入。
  • TextBox 使用 TwoWay 模式:允许用户修改数据,并更新源。
  • Label 使用 OneTime 模式:设备ID只在初始化时绑定一次。
Mermaid流程图:绑定模式数据流向
graph LR
    A[绑定源] -->|OneWay| B[绑定目标]
    A -->|TwoWay| C[绑定目标]
    C -->|TwoWay| A
    A -->|OneTime| D[绑定目标]

说明:图中展示了三种绑定模式的数据流向。TwoWay 是唯一支持双向通信的模式,适合需要交互的场景。

2.2.2 绑定更新的触发方式与性能考量

绑定的更新策略由 UpdateSourceTrigger 属性控制,决定绑定源何时更新。常见值包括:

触发时机 适用场景
Default 默认行为(取决于目标属性) 多数控件
PropertyChanged 源属性变更时立即更新 实时数据输入
LostFocus 控件失去焦点时更新 表单提交
Explicit 手动调用更新 特殊需求
示例代码:绑定更新策略
<TextBox Text="{Binding Path=Temperature, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

在此绑定中,每当用户输入内容,绑定源的 Temperature 属性都会立即更新,适用于需要实时响应的场景如传感器数据监控。

性能考量
  • PropertyChanged 会频繁触发绑定更新,可能导致性能问题,特别是在绑定大量数据或复杂逻辑时。
  • 对于非实时需求,推荐使用 LostFocus 模式减少更新频率。

2.3 数据绑定实践

在工业自动化应用中,数据绑定常用于传感器数据展示、设备状态监控等场景。通过绑定机制,可以轻松实现设备状态的实时反馈。

2.3.1 在自动化行业中绑定传感器数据

假设我们有一个温度传感器,其值由后台线程不断更新,我们希望在界面上实时显示该值。

示例代码:绑定传感器数据
public class TemperatureSensor : INotifyPropertyChanged
{
    private double currentTemp;
    public double CurrentTemp
    {
        get { return currentTemp; }
        set
        {
            if (currentTemp != value)
            {
                currentTemp = value;
                OnPropertyChanged(nameof(CurrentTemp));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    // 模拟温度变化
    public void SimulateTemperatureChange()
    {
        var rand = new Random();
        Task.Run(() =>
        {
            while (true)
            {
                CurrentTemp = Math.Round(rand.NextDouble() * 100, 2);
                Thread.Sleep(500);
            }
        });
    }
}

在XAML中绑定:

<Window.DataContext>
    <local:TemperatureSensor />
</Window.DataContext>

<TextBlock Text="{Binding Path=CurrentTemp}" />
逻辑分析
  • TemperatureSensor 类模拟传感器数据源,通过 INotifyPropertyChanged 通知绑定目标更新。
  • 后台线程每500毫秒更新一次温度值。
  • TextBlock 自动刷新显示最新温度。

2.3.2 利用绑定实现设备状态的实时反馈

在工业控制系统中,设备状态(如运行、停止、报警)通常需要实时反馈给操作员。

示例代码:设备状态绑定
public class DeviceStatus : INotifyPropertyChanged
{
    private string status;
    public string Status
    {
        get { return status; }
        set
        {
            if (status != value)
            {
                status = value;
                OnPropertyChanged(nameof(Status));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public void UpdateStatus(string newStatus)
    {
        Status = newStatus;
    }
}

XAML绑定:

<Window.DataContext>
    <local:DeviceStatus />
</Window.DataContext>

<Label Content="{Binding Path=Status}" />
逻辑分析
  • DeviceStatus 对象作为状态源。
  • 每当 Status 属性变化时, Label 会自动更新显示。
  • 这种方式非常适合用于报警提示、状态灯等需要动态变化的UI组件。

2.4 高级绑定技术

WPF的数据绑定机制不仅支持基本绑定,还提供了多种高级绑定技术,如多绑定、数据转换器、集合绑定等,极大地增强了绑定的灵活性与表现力。

2.4.1 多绑定与优先绑定

多绑定允许一个目标属性绑定到多个源属性,并通过 IMultiValueConverter 将多个值合并为一个输出值。

示例代码:多绑定
<TextBlock>
    <TextBlock.Text>
        <MultiBinding Converter="{StaticResource TemperatureConverter}">
            <Binding Path="CurrentTemp" />
            <Binding Path="Status" />
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>
数据转换器实现
public class TemperatureConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        double temp = (double)values[0];
        string status = values[1] as string;
        return $"当前温度:{temp}°C,状态:{status}";
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
逻辑分析
  • MultiBinding 允许将多个源属性(如温度和状态)合并为一个字符串。
  • 转换器负责将多个值转换为目标格式,适用于复杂的UI展示需求。

2.4.2 数据转换器(IValueConverter)的使用

数据转换器用于在绑定过程中对数据进行格式转换或逻辑处理。

示例代码:IValueConverter
<TextBlock Text="{Binding Path=CurrentTemp, Converter={StaticResource TempUnitConverter}}" />

转换器实现:

public class TempUnitConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        double temp = (double)value;
        return $"{temp} °C";
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        string tempStr = value as string;
        return double.Parse(tempStr.Replace(" °C", ""));
    }
}

说明:该转换器将数值添加单位“°C”,使显示更直观。

2.4.3 集合绑定与ItemsControl的动态生成

集合绑定用于将集合类型数据(如List、ObservableCollection)绑定到UI控件(如 ItemsControl ListBox )。

示例代码:集合绑定
public class DeviceList : INotifyPropertyChanged
{
    public ObservableCollection<string> Devices { get; set; }

    public DeviceList()
    {
        Devices = new ObservableCollection<string>();
        Devices.Add("Sensor A");
        Devices.Add("Motor B");
        Devices.Add("Valve C");
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

XAML绑定:

<Window.DataContext>
    <local:DeviceList />
</Window.DataContext>

<ItemsControl ItemsSource="{Binding Devices}" />
逻辑分析
  • ItemsControl 自动为集合中的每个元素生成UI项。
  • 使用 ObservableCollection 可确保集合变化时UI自动更新。

Mermaid流程图:ItemsControl绑定流程

graph LR
    A[绑定源集合] --> B[ItemsControl]
    B --> C[生成UI项]
    A -->|更新| C
表格:ItemsControl与其他控件对比
控件 支持绑定 支持动态更新 特点
ItemsControl 无交互,仅展示
ListBox 支持选择
ComboBox 下拉选择
DataGrid 表格展示,支持编辑

说明: ItemsControl 是展示动态集合数据的理想选择,尤其适合用于设备列表、传感器数据点等场景。

本章从基础绑定结构讲起,逐步深入到绑定模式、实践应用以及高级绑定技术,结合工业自动化中的实际需求,展示了WPF数据绑定机制的强大功能。通过代码示例、表格和流程图的辅助,帮助读者全面掌握这一关键技术,为后续UI设计与交互优化打下坚实基础。

3. 样式(Style)与模板(Template)设计

在WPF中,样式(Style)与模板(Template)是构建现代化、高复用性和高度可定制化用户界面的核心机制。它们不仅帮助开发者实现UI的一致性,还能极大地提升开发效率和界面的可维护性。本章将从样式的基础定义、控件模板的构建、数据模板的应用,到其在工业自动化HMI界面中的实际使用,逐步深入地探讨WPF样式与模板的设计与实现。

3.1 样式设计基础

3.1.1 样式定义与资源字典

WPF中的样式(Style)用于定义控件的外观和行为,它通过统一的方式设置多个控件的公共属性,避免了重复定义。样式可以定义在控件自身、页面级别,或者资源字典(Resource Dictionary)中以供全局复用。

示例代码:定义一个按钮样式
<Window.Resources>
    <Style x:Key="CustomButtonStyle" TargetType="Button">
        <Setter Property="Background" Value="#4CAF50"/>
        <Setter Property="Foreground" Value="White"/>
        <Setter Property="FontSize" Value="14"/>
        <Setter Property="Padding" Value="10,5"/>
        <Setter Property="Margin" Value="5"/>
    </Style>
</Window.Resources>

<Button Content="点击我" Style="{StaticResource CustomButtonStyle}" />
逻辑分析:
  • <Window.Resources> :定义在窗口级别的资源集合。
  • x:Key="CustomButtonStyle" :为样式设置唯一标识符,用于引用。
  • TargetType="Button" :指定该样式适用于Button控件。
  • <Setter> :设定控件的属性值。
  • Style="{StaticResource CustomButtonStyle}" :将样式绑定到按钮上。
资源字典的组织结构:

样式可以被集中管理于资源字典中,便于跨页面或跨程序集复用。例如:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style x:Key="CustomButtonStyle" TargetType="Button">
        <!-- 样式内容 -->
    </Style>
</ResourceDictionary>
资源字典的引用方式:
<Window.Resources>
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Styles/CustomStyles.xaml"/>
    </ResourceDictionary.MergedDictionaries>
</Window.Resources>

3.1.2 样式继承与覆盖机制

WPF支持样式的继承机制,允许通过 BasedOn 属性继承已有样式,并在此基础上进行修改,避免重复定义。

示例代码:样式继承
<Style x:Key="PrimaryButtonStyle" TargetType="Button">
    <Setter Property="Background" Value="#2196F3"/>
    <Setter Property="Foreground" Value="White"/>
</Style>

<Style x:Key="DangerButtonStyle" TargetType="Button" BasedOn="{StaticResource PrimaryButtonStyle}">
    <Setter Property="Background" Value="#F44336"/>
</Style>
分析:
  • BasedOn 指定继承自 PrimaryButtonStyle
  • DangerButtonStyle 保留了字体颜色为白色,但背景色被覆盖为红色。
样式覆盖的优先级:
覆盖方式 优先级 说明
控件本地属性 最高 直接在控件上设置的属性值
显式样式(x:Key) 使用资源引用的样式
默认样式(TargetType) 未指定 Key 的样式,适用于所有控件
系统样式 最低 WPF默认提供的控件样式

3.2 控件模板(ControlTemplate)

3.2.1 控件模板的结构与组成

控件模板(ControlTemplate)允许开发者完全自定义控件的视觉结构和行为,从而实现高度定制的UI效果。它通常包含 VisualStateManager TemplateBinding ContentPresenter 等关键元素。

示例:自定义按钮模板
<Style TargetType="Button" x:Key="FlatButtonStyle">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate>
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <ContentPresenter HorizontalAlignment="Center"
                                      VerticalAlignment="Center"/>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
逻辑分析:
  • <ControlTemplate> :定义控件的外观结构。
  • {TemplateBinding} :绑定控件本身的属性,如 Background、BorderBrush。
  • <ContentPresenter> :显示按钮的内容,自动居中。
状态管理:使用 VisualStateManager
<ControlTemplate>
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="CommonStates">
                <VisualState x:Name="Normal"/>
                <VisualState x:Name="MouseOver">
                    <Storyboard>
                        <ColorAnimation Storyboard.TargetName="border"
                                        Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
                                        To="#FFB3E5FC" Duration="0:0:0.2"/>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Border x:Name="border" Background="White" BorderBrush="#2196F3" BorderThickness="2">
            <ContentPresenter/>
        </Border>
    </Grid>
</ControlTemplate>
流程图:控件模板状态切换流程
stateDiagram
    [*] --> Normal
    Normal --> MouseOver : 鼠标悬停
    MouseOver --> Pressed : 鼠标按下
    Pressed --> Normal : 鼠标释放

3.2.2 自定义按钮、进度条等控件样式

示例:自定义进度条样式
<Style TargetType="ProgressBar" x:Key="CustomProgressBar">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate>
                <Border Background="#EEE" CornerRadius="5">
                    <Rectangle x:Name="PART_Track" Fill="#FFCDD2"/>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
说明:
  • PART_Track 是 ProgressBar 的模板部分名称,用于控制进度条填充区域。
  • 此样式将进度条的背景设置为灰色,进度填充区域设置为浅红色。

3.3 数据模板(DataTemplate)

3.3.1 数据模板与数据绑定的结合

数据模板(DataTemplate)用于定义数据对象在UI中呈现的方式。它通常与 ItemsControl ListBox ComboBox 等控件结合使用,实现动态数据展示。

示例:定义数据模板
<Window.Resources>
    <DataTemplate x:Key="DeviceTemplate">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding Name}" FontWeight="Bold"/>
            <TextBlock Text=" - "/>
            <TextBlock Text="{Binding Status}" Foreground="{Binding Status, Converter={StaticResource StatusToColorConverter}}"/>
        </StackPanel>
    </DataTemplate>
</Window.Resources>

<ListBox ItemsSource="{Binding Devices}" ItemTemplate="{StaticResource DeviceTemplate}"/>
逻辑分析:
  • ItemsSource :绑定设备集合。
  • ItemTemplate :指定每个设备项的显示方式。
  • {Binding Status, Converter=...} :使用转换器将状态值转换为颜色。

3.3.2 在自动化界面中展示设备数据结构

在工业自动化系统中,常需要将PLC设备、传感器等数据结构可视化。使用数据模板可以非常灵活地展示这些结构化数据。

示例:设备状态模板
<DataTemplate x:Key="DeviceStatusTemplate">
    <Border BorderBrush="#2196F3" BorderThickness="1" Padding="5">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Ellipse Width="20" Height="20" Fill="{Binding Status, Converter={StaticResource StatusToColorConverter}}"/>
            <TextBlock Grid.Column="1" Text="{Binding Name}" Margin="5,0"/>
        </Grid>
    </Border>
</DataTemplate>
效果说明:
  • 圆形指示灯颜色由状态决定(绿色表示正常,红色表示报警)。
  • 名称与状态一并显示,结构清晰。

3.4 样式与模板在工业界面中的应用

3.4.1 设计统一风格的HMI界面

在工业HMI(人机界面)开发中,一致性是用户体验的关键。样式和模板可以集中定义一套UI规范,确保所有控件在不同页面和模块中保持统一风格。

示例:统一按钮风格资源
<Style TargetType="Button" BasedOn="{StaticResource FlatButtonStyle}">
    <Setter Property="Background" Value="#4CAF50"/>
    <Setter Property="Foreground" Value="White"/>
</Style>
优势:
  • 易于维护和更新。
  • 降低视觉风格差异带来的认知负担。
  • 提升系统整体的专业感和工业感。

3.4.2 提升界面美观性与用户体验的一致性

通过模板化设计,不仅可以提升界面的美观性,还可以统一交互逻辑,例如按钮的悬停效果、状态变化动画等。

示例:统一的按钮动画样式
<Style TargetType="Button" x:Key="AnimatedButtonStyle">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate>
                <Border x:Name="border" Background="#2196F3" CornerRadius="4">
                    <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="border" Property="Background" Value="#1976D2"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
效果说明:
  • 按钮默认为蓝色。
  • 鼠标悬停时变为深蓝色,提升交互反馈。
  • 适用于所有工业操作按钮,如“启动”、“停止”、“复位”等。
总结:

在WPF开发中,样式与模板是构建现代、统一、高可维护性UI的关键工具。它们不仅提供了视觉上的灵活性,还极大地提升了代码的复用性和开发效率。在工业自动化HMI界面中,合理使用样式与模板能够显著提升界面的专业度与一致性,增强用户体验,降低后期维护成本。下一章将深入讲解WPF的布局系统与资源管理策略,帮助开发者构建响应式和结构清晰的用户界面。

4. XAML界面布局与资源管理

WPF 提供了强大的布局系统和资源管理机制,使得开发者可以灵活构建复杂的用户界面,并实现高效的资源复用。本章将深入探讨 WPF 中的布局系统、响应式设计方法、资源管理策略,并通过一个自动化控制面板的实战案例,展示如何将这些技术整合应用于实际工业场景。

4.1 布局系统基础

WPF 的布局系统基于一套灵活的布局控件,允许开发者根据不同的需求选择合适的容器来组织界面元素。掌握这些布局控件是构建复杂 UI 的第一步。

4.1.1 Panel布局控件的分类与使用场景

WPF 提供了多种 Panel 布局控件,常见的有 Grid StackPanel Canvas DockPanel WrapPanel 。它们各有特点,适用于不同的界面布局需求:

布局控件 特点 适用场景
Grid 支持行列划分,布局灵活,支持跨列跨行 复杂表格、表单、仪表盘
StackPanel 按照水平或垂直方向排列子元素 工具栏、按钮组、导航栏
Canvas 绝对定位,手动设置子元素坐标 自定义图形界面、自由拖拽布局
DockPanel 子元素可按方向停靠,适合构建窗口结构 主窗口边框布局、侧边栏
WrapPanel 子元素自动换行排列,适合动态内容 图标列表、标签云

4.1.2 Grid、StackPanel与Canvas的对比分析

我们通过一个简单的示例来对比三种常用布局控件的使用方式:

<Window x:Class="LayoutComparison.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="布局对比示例" Height="300" Width="500">
    <TabControl>
        <TabItem Header="Grid">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <TextBlock Text="用户名:" Grid.Row="0" Grid.Column="0"/>
                <TextBox Grid.Row="0" Grid.Column="1"/>
                <TextBlock Text="密码:" Grid.Row="1" Grid.Column="0"/>
                <TextBox Grid.Row="1" Grid.Column="1"/>
            </Grid>
        </TabItem>

        <TabItem Header="StackPanel">
            <StackPanel Orientation="Vertical" Margin="10">
                <TextBlock Text="用户名:"/>
                <TextBox/>
                <TextBlock Text="密码:"/>
                <TextBox/>
            </StackPanel>
        </TabItem>

        <TabItem Header="Canvas">
            <Canvas Margin="10">
                <TextBlock Text="用户名:" Canvas.Left="10" Canvas.Top="10"/>
                <TextBox Canvas.Left="100" Canvas.Top="10" Width="200"/>
                <TextBlock Text="密码:" Canvas.Left="10" Canvas.Top="40"/>
                <TextBox Canvas.Left="100" Canvas.Top="40" Width="200"/>
            </Canvas>
        </TabItem>
    </TabControl>
</Window>

逐行解析:

  • Grid 使用 RowDefinitions ColumnDefinitions 定义行列结构,通过 Grid.Row Grid.Column 设置控件位置。
  • StackPanel 使用 Orientation 属性控制子元素排列方向,简单易用但不够灵活。
  • Canvas 需要手动设置每个子元素的 Canvas.Left Canvas.Top 坐标,适合绝对定位需求。

逻辑分析:

  • Grid 是最常用的布局控件,适合需要行列对齐的场景。
  • StackPanel 适用于简单的垂直或水平排列。
  • Canvas 更适合图形界面或需要绝对定位的元素,但不利于响应式设计。

4.2 响应式布局设计

在现代应用中,不同设备的屏幕分辨率差异巨大,因此响应式布局设计显得尤为重要。

4.2.1 使用约束布局与自动调整机制

WPF 提供了丰富的机制来实现响应式布局,例如使用 Grid 的自动调整、 ViewBox 的缩放功能以及 DataTrigger 的绑定逻辑。

<Window x:Class="ResponsiveLayout.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="响应式布局示例" Height="300" Width="500">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="2*"/>
        </Grid.ColumnDefinitions>
        <TextBlock Text="左侧内容" Background="LightBlue" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <TextBlock Text="右侧内容" Grid.Column="1" Background="LightGreen" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Grid>
</Window>

参数说明:

  • Width="*" 表示该列自动适应剩余空间。
  • Width="2*" 表示该列是其他 * 列的两倍宽度。

逻辑分析:

  • 上述代码使用 Grid 实现了一个简单的响应式布局,左右两列根据窗口大小自动调整比例。
  • 这种布局方式适合窗口大小变化频繁的场景,例如工业 HMI 界面。

4.2.2 支持不同屏幕分辨率的适配策略

WPF 可以通过绑定屏幕分辨率并动态调整布局。例如:

<Window x:Class="DynamicLayout.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="动态布局适配" Height="300" Width="500"
        SizeChanged="Window_SizeChanged">
    <Grid x:Name="MainGrid">
        <TextBlock x:Name="ResolutionText" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Grid>
</Window>
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
    var width = (int)e.NewSize.Width;
    var height = (int)e.NewSize.Height;
    ResolutionText.Text = $"当前分辨率:{width} x {height}";
    if (width < 400)
    {
        MainGrid.RowDefinitions.Clear();
        MainGrid.ColumnDefinitions.Clear();
        MainGrid.Children.Clear();

        MainGrid.RowDefinitions.Add(new RowDefinition());
        MainGrid.RowDefinitions.Add(new RowDefinition());

        TextBlock tb1 = new TextBlock { Text = "顶部内容", Background = Brushes.LightBlue };
        TextBlock tb2 = new TextBlock { Text = "底部内容", Background = Brushes.LightGreen };

        Grid.SetRow(tb1, 0);
        Grid.SetRow(tb2, 1);

        MainGrid.Children.Add(tb1);
        MainGrid.Children.Add(tb2);
    }
    else
    {
        MainGrid.RowDefinitions.Clear();
        MainGrid.ColumnDefinitions.Clear();
        MainGrid.Children.Clear();

        MainGrid.ColumnDefinitions.Add(new ColumnDefinition());
        MainGrid.ColumnDefinitions.Add(new ColumnDefinition());

        TextBlock tb1 = new TextBlock { Text = "左侧内容", Background = Brushes.LightBlue };
        TextBlock tb2 = new TextBlock { Text = "右侧内容", Background = Brushes.LightGreen };

        Grid.SetColumn(tb1, 0);
        Grid.SetColumn(tb2, 1);

        MainGrid.Children.Add(tb1);
        MainGrid.Children.Add(tb2);
    }
}

逻辑分析:

  • 当窗口大小变化时,触发 SizeChanged 事件,动态调整布局结构。
  • 如果窗口宽度小于 400 像素,改为垂直布局;否则保持水平布局。
  • 这种方式适用于不同分辨率下的 UI 适配,在工业自动化中尤为常见。

4.3 资源管理与共享

在大型 WPF 项目中,资源的统一管理至关重要。WPF 提供了静态资源(StaticResource)和动态资源(DynamicResource)两种机制。

4.3.1 静态资源与动态资源的区别

类型 加载方式 是否支持运行时修改 适用场景
StaticResource 编译时加载 不变的样式、图标、颜色等
DynamicResource 运行时动态加载 主题切换、皮肤切换、动态样式等

示例代码:

<Window.Resources>
    <Style x:Key="ButtonStyle" TargetType="Button">
        <Setter Property="Background" Value="LightBlue"/>
        <Setter Property="FontSize" Value="14"/>
    </Style>
</Window.Resources>

<Button Content="静态资源按钮" Style="{StaticResource ButtonStyle}" Margin="10"/>
<Button Content="动态资源按钮" Style="{DynamicResource ButtonStyle}" Margin="10"/>

逻辑分析:

  • StaticResource 在编译时确定资源,适用于固定样式。
  • DynamicResource 在运行时查找资源,适用于需要动态切换的场景,如主题切换。

4.3.2 资源字典的组织与复用方式

资源字典(ResourceDictionary)是组织和复用资源的有效方式。可以通过合并多个资源字典实现全局样式统一。

<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="Styles/Buttons.xaml"/>
    <ResourceDictionary Source="Styles/Colors.xaml"/>
</ResourceDictionary.MergedDictionaries>

逻辑分析:

  • 每个资源字典可以管理一组样式、颜色或字体资源。
  • 通过合并字典,可以在多个页面或窗口中复用资源,提升开发效率和维护性。

4.4 实战:构建自动化控制面板界面

在工业自动化中,控制面板是常见的界面形式。我们将使用 XAML 实现一个包含按钮、图表、状态指示灯的控制面板。

4.4.1 使用XAML布局实现主控界面

<Window x:Class="ControlPanel.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="自动化控制面板" Height="600" Width="800">
    <DockPanel>
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="文件"/>
            <MenuItem Header="设置"/>
        </Menu>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>

            <StackPanel Orientation="Horizontal" Grid.Row="0">
                <Button Content="启动" Style="{StaticResource ControlButton}"/>
                <Button Content="停止" Style="{StaticResource ControlButton}"/>
                <Button Content="重置" Style="{StaticResource ControlButton}"/>
            </StackPanel>

            <Border Grid.Row="1" BorderBrush="Gray" BorderThickness="1" Margin="10">
                <ScrollViewer>
                    <ItemsControl ItemsSource="{Binding Devices}">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <Grid Margin="5" Background="White">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="100"/>
                                        <ColumnDefinition Width="*"/>
                                    </Grid.ColumnDefinitions>
                                    <TextBlock Text="{Binding Name}" Grid.Column="0" VerticalAlignment="Center"/>
                                    <ProgressBar Value="{Binding Status}" Grid.Column="1" Height="20" Minimum="0" Maximum="100"/>
                                </Grid>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </ScrollViewer>
            </Border>
        </Grid>
    </DockPanel>
</Window>

逻辑分析:

  • 使用 DockPanel 构建主框架结构,顶部为菜单栏,下方为内容区域。
  • 控制按钮使用统一的 StaticResource 样式,提升界面一致性。
  • 设备状态使用 ItemsControl 动态生成,每个设备显示名称和状态进度条。

4.4.2 管理界面中的图标、字体与颜色资源

在实际开发中,我们可以将图标、字体、颜色等资源统一管理在资源字典中:

<!-- Styles/Colors.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Color x:Key="PrimaryColor">#FF3F51B5</Color>
    <SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource PrimaryColor}"/>
</ResourceDictionary>

<!-- Styles/Buttons.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style x:Key="ControlButton" TargetType="Button">
        <Setter Property="Background" Value="{StaticResource PrimaryBrush}"/>
        <Setter Property="Foreground" Value="White"/>
        <Setter Property="Margin" Value="5"/>
        <Setter Property="Padding" Value="10,5"/>
    </Style>
</ResourceDictionary>

逻辑分析:

  • 通过资源字典集中管理颜色和样式,便于统一修改和维护。
  • 在多个页面中引用相同的资源字典,实现界面风格统一。

本章通过深入分析 WPF 的布局系统与资源管理机制,结合工业自动化控制面板的实战案例,展示了如何构建高效、灵活、响应式的用户界面。下一章将继续深入探讨 WPF 在工业自动化中的高级应用,包括多线程处理、实时数据展示等核心内容。

5. 自动化行业UI设计需求分析与实践

5.1 工业自动化界面设计特点

工业自动化系统对界面设计有着特殊的要求,不同于通用型软件,其UI设计需兼顾以下几个核心特性:

  • 实时性 :界面需能够实时反映设备状态、传感器数据、报警信息等。
  • 稳定性 :系统需长时间运行,界面不能出现崩溃、卡顿等现象。
  • 可维护性 :界面逻辑清晰,结构易于维护和扩展。
  • 交互逻辑清晰 :操作流程简洁,符合工业操作人员的使用习惯。

在WPF中,通过数据绑定、样式控制、模板机制和异步处理等手段,可以很好地满足这些要求。此外,工业UI往往涉及大量的图表、状态指示、动态数据更新等元素,WPF的图形渲染能力也为其提供了良好的支持。

5.2 侧滑菜单(Drawer Menu)设计与实现

5.2.1 动态菜单的XAML实现方式

在工业HMI中,侧滑菜单常用于快速切换操作界面或查看系统设置。WPF中可以使用 Grid TranslateTransform 和动画实现平滑的侧滑效果。

<Grid>
    <!-- 主界面 -->
    <Border x:Name="MainContent" Background="White">
        <!-- 主界面内容 -->
    </Border>

    <!-- 侧滑菜单 -->
    <Border x:Name="SideMenu" Width="200" Background="#333" HorizontalAlignment="Left">
        <TranslateTransform x:Name="MenuTransform" X="-200"/>
    </Border>

    <!-- 触发按钮 -->
    <Button Content="☰" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10" Click="ToggleMenu_Click"/>
</Grid>

在后台代码中,通过修改 TranslateTransform X 值来实现菜单的显示与隐藏:

private void ToggleMenu_Click(object sender, RoutedEventArgs e)
{
    DoubleAnimation animation = new DoubleAnimation();
    if (MenuTransform.X == 0)
    {
        animation.To = -200; // 隐藏菜单
    }
    else
    {
        animation.To = 0; // 显示菜单
    }
    animation.Duration = TimeSpan.FromSeconds(0.3);
    MenuTransform.BeginAnimation(TranslateTransform.XProperty, animation);
}

5.2.2 菜单与主界面的联动交互

当菜单滑出时,主界面通常需要进行位移或半透明处理,以增强用户体验。可以通过绑定或代码方式联动:

// 主界面位移动画
DoubleAnimation mainAnim = new DoubleAnimation();
mainAnim.To = (MenuTransform.X == 0) ? 200 : 0;
mainAnim.Duration = TimeSpan.FromSeconds(0.3);
MainContent.RenderTransform = new TranslateTransform();
MainContent.RenderTransform.BeginAnimation(TranslateTransform.XProperty, mainAnim);

// 背景透明度变化
MainContent.Background = (MenuTransform.X == 0) ? Brushes.LightGray : Brushes.White;

这种联动机制不仅增强了视觉效果,也提升了用户对界面操作的理解与掌控感。

5.3 实时数据展示与设备状态监控

5.3.1 使用图表控件展示设备运行数据

在工业自动化中,常用折线图、柱状图来展示温度、压力、流量等参数变化。WPF中可使用 LiveCharts 库实现动态图表。

首先安装 NuGet 包:

Install-Package LiveCharts.Wpf

XAML 中引入命名空间并添加图表控件:

<Window xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf">
    <lvc:CartesianChart x:Name="TemperatureChart" />
</Window>

后台代码中初始化数据:

public SeriesCollection SeriesCollection { get; set; }

public MainWindow()
{
    InitializeComponent();
    SeriesCollection = new SeriesCollection
    {
        new LineSeries
        {
            Title = "温度传感器",
            Values = new ChartValues<double> { 20, 22, 21, 24, 23 }
        }
    };
    TemperatureChart.Series = SeriesCollection;
}

// 模拟实时更新
private void UpdateTemperature(double value)
{
    SeriesCollection[0].Values.Add(value);
    if (SeriesCollection[0].Values.Count > 20)
        SeriesCollection[0].Values.RemoveAt(0);
}

5.3.2 状态指示灯与报警提示机制设计

使用 Ellipse Trigger 实现状态指示灯,颜色变化代表设备状态:

<Ellipse Width="20" Height="20">
    <Ellipse.Fill>
        <SolidColorBrush Color="{Binding DeviceStatus, Converter={StaticResource StatusToColorConverter}}" />
    </Ellipse.Fill>
</Ellipse>

配合 IValueConverter 实现状态到颜色的转换:

public class StatusToColorConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        switch ((string)value)
        {
            case "Running": return Colors.Green;
            case "Stopped": return Colors.Red;
            case "Warning": return Colors.Yellow;
            default: return Colors.Gray;
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

报警提示可结合弹窗、声音提示等方式,提升用户感知。

5.4 多线程处理提升界面响应性能

5.4.1 Dispatcher机制与UI线程安全

WPF中所有UI操作必须在主线程(Dispatcher线程)中执行。若在后台线程直接操作UI会抛出异常。可使用 Dispatcher.Invoke 安全更新界面:

Application.Current.Dispatcher.Invoke(() =>
{
    StatusText.Text = "设备状态更新";
});

5.4.2 后台线程与异步加载数据的实践

使用 Task.Run 在后台线程处理耗时任务,避免UI冻结:

private async void LoadDeviceData()
{
    await Task.Run(() =>
    {
        // 模拟长时间数据读取
        Thread.Sleep(2000);
        string data = GetDeviceData();

        Application.Current.Dispatcher.Invoke(() =>
        {
            DeviceInfo.Text = data;
        });
    });
}

这种方式能有效提升用户体验,尤其在工业现场数据采集频繁的场景中非常关键。

(未完待续)

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:WPF作为微软.NET平台下的现代化UI框架,凭借其强大的数据绑定、样式模板和可视化组件能力,广泛应用于自动化行业的界面开发。本项目围绕一个基于WPF的DrawerMenuStyle解决方案展开,使用VS2022开发环境,结合DOT6运行环境,展示如何构建一个专业、美观且高效响应的自动化控制界面框架。项目内容涵盖界面布局、多线程处理、样式设计与菜单交互实现,适合希望提升WPF实战能力并应用于工业自动化的开发者学习和拓展。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

您可能感兴趣的与本文相关的镜像

TensorFlow-v2.9

TensorFlow-v2.9

TensorFlow

TensorFlow 是由Google Brain 团队开发的开源机器学习框架,广泛应用于深度学习研究和生产环境。 它提供了一个灵活的平台,用于构建和训练各种机器学习模型

ModernUI(http://mui.codeplex.com/)是一个开源的WPF界面库,利用该界面库,我们可以创建很酷的应用程序。下面是ModernUI官方示例,你可以从官方网站直接下载源码运行,如果是.NET 4.0的话,记得要声明“NET4”预编译变量,否则无法编译通过。 这个界面框架是基于ModernUI来实现的,在该文我将分享所有的源码,并详细描述如何基于ModernUI来构造一个非常通用的、插件化的WPF开发框架。下载源码的同志,希望点击一下推荐。 本文将按照以下四点来介绍: (1)ModernUI简介; (2)构建通用界面框架的思路; (3)基于ModernUI和OSGi.NET的插件化界面框架实现原理及源码分析; (4)其它更有趣的东西~~。 要编写这样的WPF界面,我们需要在一个Window上声明菜单和Tab页面,下图是定义菜单的声明。 此外,每一个Tab风格页面,你也需要手动的为菜单创建这样的界面元素。 直接用这样的方式来使用ModernUI,显然不太适合团队协作性的并行开发,因为在一个团队的协作中,不同的人需要完成不同的功能,实现不同页面,每个人都需要来更改主界面。 我非常希望模块化的开发方法,因为这可以尽可能的复用现有资产,使程序员可以聚焦在自己关注的业务逻辑上,不需要关心UI的使用。下面,我将来描述基于ModernUI实现的一个通用界面框架,这个界面框架允许程序员在自己的业务模块中配置需要显示的界面元素。 通用界面框架实现思路: 我希望能够实现这样的通用界面框架: (1)程序员可以直接实现需要展现业务逻辑的界面,不需要关注如何使用ModernUI; (2)程序员可以通过简单的配置就可以将自己实现的业务逻辑页面显示在主界面中; (3)这个界面框架可以完全复用。 当我看到ModernUI这个界面库时,我希望将应用程序做成模块化,每一个模块能够: (1)通过以下配置能够直接显示二级菜单。 (2)通过以下配置能够直接显示三级菜单。 这样做的好处是,开发插件的时候可以不需要关心界面框架插件;团队在协作开发应用的时候,可以独立开发并不需要修改主界面;团队成员的插件可以随时集成到这个主界面;当主界面无法满足我们的布局时或者用户需求无法满足时,可以直接替换主界面框架而不需要修改任何插件代码。 最终的效果如下,以下界面的几个菜单及点击菜单显示的内容由DemoPlugin插件、DemoPlugin2插件来提供。当插件框架加载更多插件时,界面上会出现更多的菜单;反之,当插件被卸载或者被停止时,则相应的菜单将消失掉。
ModernUI(http://mui.codeplex.com/)是一个开源的WPF界面库,利用该界面库,我们可以创建很酷的应用程序。下面是ModernUI官方示例,你可以从官方网站直接下载源码运行,如果是.NET 4.0的话,记得要声明“NET4”预编译变量,否则无法编译通过。 这个界面框架是基于ModernUI来实现的,在该文我将分享所有的源码,并详细描述如何基于ModernUI来构造一个非常通用的、插件化的WPF开发框架。下载源码的同志,希望点击一下推荐。 本文将按照以下四点来介绍: (1)ModernUI简介; (2)构建通用界面框架的思路; (3)基于ModernUI和OSGi.NET的插件化界面框架实现原理及源码分析; (4)其它更有趣的东西~~。 要编写这样的WPF界面,我们需要在一个Window上声明菜单和Tab页面,下图是定义菜单的声明。 此外,每一个Tab风格页面,你也需要手动的为菜单创建这样的界面元素。 直接用这样的方式来使用ModernUI,显然不太适合团队协作性的并行开发,因为在一个团队的协作中,不同的人需要完成不同的功能,实现不同页面,每个人都需要来更改主界面。 我非常希望模块化的开发方法,因为这可以尽可能的复用现有资产,使程序员可以聚焦在自己关注的业务逻辑上,不需要关心UI的使用。下面,我将来描述基于ModernUI实现的一个通用界面框架,这个界面框架允许程序员在自己的业务模块中配置需要显示的界面元素。 通用界面框架实现思路: 我希望能够实现这样的通用界面框架: (1)程序员可以直接实现需要展现业务逻辑的界面,不需要关注如何使用ModernUI; (2)程序员可以通过简单的配置就可以将自己实现的业务逻辑页面显示在主界面中; (3)这个界面框架可以完全复用。 当我看到ModernUI这个界面库时,我希望将应用程序做成模块化,每一个模块能够: (1)通过以下配置能够直接显示二级菜单。 (2)通过以下配置能够直接显示三级菜单。 这样做的好处是,开发插件的时候可以不需要关心界面框架插件;团队在协作开发应用的时候,可以独立开发并不需要修改主界面;团队成员的插件可以随时集成到这个主界面;当主界面无法满足我们的布局时或者用户需求无法满足时,可以直接替换主界面框架而不需要修改任何插件代码。 最终的效果如下,以下界面的几个菜单及点击菜单显示的内容由DemoPlugin插件、DemoPlugin2插件来提供。当插件框架加载更多插件时,界面上会出现更多的菜单;反之,当插件被卸载或者被停止时,则相应的菜单将消失掉。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值