WPF基础知识(续)

六、WPF 中的样式和模板

  • 样式定义
    • 可以在 XAML 中定义样式来统一 UI 元素的外观和风格。样式可以定义在资源字典中,也可以直接在窗口或控件的Resources属性中定义。例如,定义一个按钮的样式:
<Window.Resources>
    <Style TargetType="Button">
        <Setter Property="Background" Value="Blue" />
        <Setter Property="Foreground" Value="White" />
        <Setter Property="FontSize" Value="16" />
    </Style>
</Window.Resources>
<Grid>
    <Button Content="按钮1" />
    <Button Content="按钮2" />
</Grid>

  • 也可以为样式指定一个Key,然后通过StaticResource来应用样式。例如:
<Window.Resources>
    <Style x:Key="MyButtonStyle" TargetType="Button">
        <Setter Property="Background" Value="Green" />
        <Setter Property="Foreground" Value="Yellow" />
        <Setter Property="FontSize" Value="18" />
    </Style>
</Window.Resources>
<Grid>
    <Button Content="按钮1" Style="{StaticResource MyButtonStyle}" />
    <Button Content="按钮2" />
</Grid>

  • 模板创建
    • 可以创建控件模板来完全自定义控件的外观。例如,创建一个自定义的按钮模板:
<Window.Resources>
    <ControlTemplate x:Key="MyButtonTemplate" TargetType="Button">
        <Grid>
            <Rectangle Fill="{TemplateBinding Background}" />
            <TextBlock Text="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Grid>
    </ControlTemplate>
    <Style x:Key="MyButtonStyle" TargetType="Button">
        <Setter Property="Template" Value="{StaticResource MyButtonTemplate}" />
    </Style>
</Window.Resources>
<Grid>
    <Button Content="按钮1" Style="{StaticResource MyButtonStyle}" Background="Red" />
</Grid>

七、WPF 中的动画

  • 简单动画
    • 可以使用Storyboard来定义动画。例如,实现一个按钮的宽度从 100 逐渐增加到 200 的动画:
<Window.Resources>
    <Storyboard x:Key="ButtonWidthAnimation">
        <DoubleAnimation Storyboard.TargetProperty="Width" From="100" To="200" Duration="0:0:5" />
    </Storyboard>
</Window.Resources>
<Grid>
    <Button Content="按钮1" Width="100" Click="Button_Click">
        <Button.Triggers>
            <EventTrigger RoutedEvent="Button.Click">
                <BeginStoryboard Storyboard="{StaticResource ButtonWidthAnimation}" />
            </EventTrigger>
        </Button.Triggers>
    </Button>
</Grid>

  • 复杂动画
    • 可以组合多个动画来实现更复杂的效果。例如,同时实现按钮的宽度增加和颜色变化的动画:
<Window.Resources>
    <Storyboard x:Key="ButtonAnimation">
        <DoubleAnimation Storyboard.TargetProperty="Width" From="100" To="200" Duration="0:0:5" />
        <ColorAnimation Storyboard.TargetProperty="(Button.Background).(SolidColorBrush.Color)" From="Red" To="Blue" Duration="0:0:5" />
    </Storyboard>
</Window.Resources>
<Grid>
    <Button Content="按钮1" Width="100" Background="Red" Click="Button_Click">
        <Button.Triggers>
            <EventTrigger RoutedEvent="Button.Click">
                <BeginStoryboard Storyboard="{StaticResource ButtonAnimation}" />
            </EventTrigger>
        </Button.Triggers>
    </Button>
</Grid>

八、WPF 应用程序的部署

  • 打包应用程序
    • 可以使用 Visual Studio 的发布功能来打包 WPF 应用程序。在项目属性中选择 “发布” 选项卡,然后按照向导进行操作,可以选择发布到本地文件夹、网络共享或创建安装包等。
    • 可以设置发布的目标平台、版本号、更新策略等选项。例如,如果希望应用程序能够自动更新,可以设置相关的更新选项,指定更新服务器的地址等。
  • 安装包创建
    • 可以使用专门的安装包制作工具,如 Inno Setup、WiX 等,来创建更复杂的安装包。这些工具可以让你更精细地控制安装过程,包括添加自定义安装界面、设置安装目录、注册系统服务等。
    • 例如,使用 Inno Setup 创建安装包时,需要编写一个脚本文件,定义安装包的各种属性和安装步骤。在脚本中可以指定应用程序的文件、图标、安装目录、开始菜单快捷方式等信息。

九、WPF 中的命令模式

1. 命令基础概念

在 WPF 里,命令模式是一种实现交互逻辑与 UI 分离的有效方式。它将操作封装成对象,这样就能在不同的 UI 元素之间复用,也方便进行测试和维护。WPF 自带了ICommand接口,这是命令的核心抽象,任何实现了该接口的类都能当作命令来用。

2. 自定义命令实现

下面是一个自定义命令的示例:

using System;
using System.Windows.Input;

public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Predicate<object> _canExecute;

    public RelayCommand(Action<object> execute) : this(execute, null)
    {
    }

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute == null || _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }
}

在上述代码中,RelayCommand类实现了ICommand接口。_execute字段存储要执行的操作,_canExecute字段用于判断命令是否可以执行。

3. 命令在 XAML 中的使用

以下是如何在 XAML 中使用自定义命令:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApp1"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Grid>
        <Button Content="执行命令" Command="{Binding MyCommand}" />
    </Grid>
</Window>

在这个例子中,按钮的Command属性绑定到了MainViewModel中的MyCommand命令。

扫描二维码关注公众号,回复: 17552732 查看本文章
4. 命令在 ViewModel 中的实现
using System.Windows.Input;

public class MainViewModel
{
    public ICommand MyCommand { get; private set; }

    public MainViewModel()
    {
        MyCommand = new RelayCommand(ExecuteMyCommand);
    }

    private void ExecuteMyCommand(object parameter)
    {
        // 这里是命令执行的具体逻辑
    }
}

MainViewModel类中,创建了一个RelayCommand实例并赋值给MyCommand属性,当按钮被点击时,ExecuteMyCommand方法就会被调用。

十、WPF 中的多线程

1. 线程安全问题

在 WPF 中,UI 元素只能由创建它们的线程来访问和修改,这就是所谓的单线程单元(STA)模型。如果在其他线程中尝试访问 UI 元素,就会抛出InvalidOperationException异常。因此,在进行多线程操作时,需要特别注意线程安全问题。

2. 使用Dispatcher更新 UI

Dispatcher是 WPF 中用于处理线程间通信的机制。可以使用Dispatcher.InvokeDispatcher.BeginInvoke方法来在 UI 线程中执行代码。以下是一个简单的示例:

using System;
using System.Threading;
using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            Thread workerThread = new Thread(DoWork);
            workerThread.Start();
        }

        private void DoWork()
        {
            // 模拟耗时操作
            Thread.Sleep(2000);

            // 使用Dispatcher更新UI
            this.Dispatcher.Invoke(() =>
            {
                MessageBox.Show("工作完成");
            });
        }
    }
}

在这个例子中,创建了一个新线程来执行DoWork方法,在DoWork方法中使用Dispatcher.Invoke方法在 UI 线程中显示消息框。

3. 使用Taskasync/await

在.NET 中,Taskasync/await是处理异步操作的推荐方式。以下是一个使用async/await的示例:

using System;
using System.Threading.Tasks;
using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DoAsyncWork();
        }

        private async void DoAsyncWork()
        {
            await Task.Run(() =>
            {
                // 模拟耗时操作
                Thread.Sleep(2000);
            });

            MessageBox.Show("异步工作完成");
        }
    }
}

在这个例子中,DoAsyncWork方法被标记为async,使用await关键字等待Task.Run方法返回的任务完成,之后的代码会在 UI 线程中继续执行,这样就避免了直接使用Dispatcher的复杂性。

十一、WPF 中的资源管理

1. 资源基础概念

在 WPF 中,资源是可以被多个元素共享的对象,例如画笔、画刷、样式、模板等。资源可以定义在多个位置,如窗口、控件、应用程序级别等。

2. 资源的定义和使用

以下是在窗口资源中定义一个画刷资源并使用它的示例:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <SolidColorBrush x:Key="MyBrush" Color="Red"/>
    </Window.Resources>
    <Grid>
        <Button Content="按钮" Background="{StaticResource MyBrush}" />
    </Grid>
</Window>

在这个例子中,在Window.Resources中定义了一个名为MyBrush的画刷资源,然后在按钮的Background属性中使用StaticResource引用该资源。

3. 资源的查找顺序

WPF 在查找资源时,会按照一定的顺序进行。首先会在当前元素的资源字典中查找,如果找不到,会向上查找父元素的资源字典,直到找到资源或者到达应用程序级别的资源字典。如果仍然找不到,就会抛出ResourceReferenceKeyNotFoundException异常。

4. 动态资源和静态资源
  • 静态资源:使用StaticResource标记扩展,在编译时解析资源引用。一旦解析完成,就不会再改变。
  • 动态资源:使用DynamicResource标记扩展,在运行时解析资源引用。如果资源发生变化,使用该资源的元素会自动更新。以下是使用动态资源的示例:
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <SolidColorBrush x:Key="MyBrush" Color="Red"/>
    </Window.Resources>
    <Grid>
        <Button Content="按钮" Background="{DynamicResource MyBrush}" />
    </Grid>
</Window>

十二、WPF 中的打印功能

1. 基本打印流程

在 WPF 中实现打印功能,通常需要以下几个步骤:

  • 创建PrintDialog对象,用于显示打印对话框。
  • 获取要打印的内容,例如Visual对象。
  • 调用PrintDialogPrintVisual方法进行打印。
2. 简单打印示例
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            PrintButton.Click += PrintButton_Click;
        }

        private void PrintButton_Click(object sender, RoutedEventArgs e)
        {
            PrintDialog printDialog = new PrintDialog();
            if (printDialog.ShowDialog() == true)
            {
                // 创建一个简单的可视化内容
                StackPanel printContent = new StackPanel();
                TextBlock textBlock = new TextBlock();
                textBlock.Text = "这是要打印的内容";
                printContent.Children.Add(textBlock);

                // 打印可视化内容
                printDialog.PrintVisual(printContent, "打印内容");
            }
        }
    }
}

在这个示例中,点击按钮会弹出打印对话框,用户选择打印设置后,会打印一个包含文本的StackPanel

3. 打印复杂内容

如果要打印复杂的内容,例如文档、报表等,可以使用DocumentPaginatorFixedDocument等类。以下是一个打印FixedDocument的示例:

private void PrintFixedDocument()
{
    PrintDialog printDialog = new PrintDialog();
    if (printDialog.ShowDialog() == true)
    {
        FixedDocument fixedDocument = new FixedDocument();

        // 创建一个页面
        FixedPage fixedPage = new FixedPage();
        TextBlock textBlock = new TextBlock();
        textBlock.Text = "这是固定文档的内容";
        fixedPage.Children.Add(textBlock);

        PageContent pageContent = new PageContent();
        ((IAddChild)pageContent).AddChild(fixedPage);
        fixedDocument.Pages.Add(pageContent);

        // 打印固定文档
        printDialog.PrintDocument(fixedDocument.DocumentPaginator, "打印固定文档");
    }
}

十三、WPF 中的触控和手势支持

1. 触控事件

WPF 支持多种触控事件,如TouchDownTouchMoveTouchUp等。可以通过处理这些事件来实现与触控设备的交互。以下是一个简单的示例:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid TouchDown="Grid_TouchDown">
        <TextBlock Text="触摸我" FontSize="24" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</Window>

private void Grid_TouchDown(object sender, TouchEventArgs e)
{
    MessageBox.Show("触摸事件触发");
}

在这个示例中,当用户触摸网格时,会弹出消息框。

2. 手势识别

WPF 还支持手势识别,例如捏合、旋转等手势。可以使用ManipulationStartingManipulationDeltaManipulationCompleted等事件来处理手势。以下是一个简单的捏合手势示例:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid ManipulationStarting="Grid_ManipulationStarting" ManipulationDelta="Grid_ManipulationDelta">
        <Rectangle Width="100" Height="100" Fill="Red" RenderTransformOrigin="0.5,0.5">
            <Rectangle.RenderTransform>
                <ScaleTransform />
            </Rectangle.RenderTransform>
        </Rectangle>
    </Grid>
</Window>

private void Grid_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
{
    e.ManipulationContainer = this;
    e.Handled = true;
}

private void Grid_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
    Rectangle rectangle = sender as Rectangle;
    ScaleTransform scaleTransform = rectangle.RenderTransform as ScaleTransform;

    scaleTransform.ScaleX *= e.DeltaManipulation.Scale.X;
    scaleTransform.ScaleY *= e.DeltaManipulation.Scale.Y;

    e.Handled = true;
}

在这个示例中,用户可以通过捏合手势来缩放矩形。

十四、WPF 中的性能优化

1. 控件虚拟化

当需要显示大量数据时,使用控件虚拟化可以显著提高性能。例如,ListBoxListView等控件都支持虚拟化。可以通过设置VirtualizingStackPanel.IsVirtualizing属性为True来启用虚拟化。

<ListBox VirtualizingStackPanel.IsVirtualizing="True">
    <!-- 大量数据项 -->
</ListBox>

启用虚拟化后,只有当前可见的项会被创建和渲染,而不是一次性创建所有项。

2. 减少布局计算

布局计算是一个比较耗时的操作,因此可以尽量减少布局的嵌套层次。例如,避免使用过多的嵌套GridStackPanel。另外,使用MeasureArrange方法的优化版本,如MeasureOverrideArrangeOverride,可以自定义布局行为,减少不必要的计算。

3. 资源管理优化

合理管理资源,避免创建过多的资源对象。例如,对于一些常用的画刷、画笔等资源,可以定义为静态资源并在多个地方共享使用。另外,及时释放不再使用的资源,避免内存泄漏。

4. 图像优化

如果应用程序中使用了大量的图像,要注意图像的格式和大小。使用合适的图像格式,如 PNG、JPEG 等,并且对图像进行压缩处理,减少图像的文件大小。另外,使用BitmapCache可以缓存图像,提高渲染性能。

<Image Source="image.jpg">
    <Image.CacheMode>
        <BitmapCache />
    </Image.CacheMode>
</Image>

十五、WPF 与数据库交互

1. 连接数据库

在 WPF 中可以使用多种方式连接数据库,例如使用Entity FrameworkADO.NET等。以下是一个使用ADO.NET连接 SQL Server 数据库的示例:

using System;
using System.Data.SqlClient;

namespace WpfApp1
{
    class DatabaseHelper
    {
        public static void ConnectToDatabase()
        {
            string connectionString = "Data Source=YOUR_SERVER_NAME;Initial Catalog=YOUR_DATABASE_NAME;User ID=YOUR_USERNAME;Password=YOUR_PASSWORD";
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                try
                {
                    connection.Open();
                    Console.WriteLine("数据库连接成功");
                }
                catch (Exception ex)
                {
                    Console.WriteLine("数据库连接失败: " + ex.Message);
                }
            }
        }
    }
}
2. 数据查询和显示

连接数据库后,可以进行数据查询并将结果显示在 WPF 界面上。以下是一个简单的查询示例:

private void DisplayData()
{
    string connectionString = "Data Source=YOUR_SERVER_NAME;Initial Catalog=YOUR_DATABASE_NAME;User ID=YOUR_USERNAME;Password=YOUR_PASSWORD";
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        string query = "SELECT * FROM YourTable";
        SqlCommand command = new SqlCommand(query, connection);
        try
        {
            connection.Open();
            SqlDataReader reader = command.ExecuteReader();
            while (reader.Read())
            {
                // 处理查询结果
                string column1Value = reader["Column1"].ToString();
                // 显示数据
            }
            reader.Close();
        }
        catch (Exception ex)
        {
            MessageBox.Show("数据查询失败: " + ex.Message);
        }
    }
}
3. 使用Entity Framework

Entity Framework是一个强大的对象关系映射(ORM)框架,可以简化数据库操作。以下是一个使用Entity Framework的示例:

using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;

namespace WpfApp1
{
    public class MyDbContext : DbContext
    {
        public DbSet<YourEntity> YourEntities { get; set; }
    }

    public class YourEntity
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    class Program
    {
        static void Main()
        {
            using (MyDbContext context = new MyDbContext())
            {
                var entities = context.YourEntities.ToList();
                foreach (var entity in entities)
                {
                    // 处理实体数据
                }
            }
        }
    }
}

使用Entity Framework可以将数据库表映射为实体类,通过操作实体类来进行数据库操作,避免了直接编写 SQL 语句的复杂性。