目录

C-WPF-基础知识学习二

C# WPF 基础知识学习(二)

四、数据绑定

(一)数据绑定基础

  1. 绑定源和目标 :数据绑定建立了 UI 元素(绑定目标)属性与数据源(绑定源)之间的联系。例如,将一个 TextBoxText 属性绑定到一个对象的某个属性上。绑定源可以是对象的属性、集合、XML 数据等,绑定目标通常是 UI 元素的依赖属性。
  2. 绑定模式 :WPF 支持三种绑定模式:
    • OneWay :数据从绑定源流向绑定目标。当绑定源属性值发生变化时,绑定目标属性会自动更新,但绑定目标的变化不会影响绑定源。例如,将一个只读的文本框绑定到一个数据模型中的属性,用于显示数据。
<TextBox Text="{Binding MyReadOnlyProperty, Mode=OneWay}"/>
  • TwoWay :数据在绑定源和绑定目标之间双向流动。当绑定源属性值变化时,绑定目标更新;当绑定目标属性值变化时,绑定源也会相应更新。常用于需要用户输入并更新数据模型的场景,如编辑文本框。
<TextBox Text="{Binding MyEditableProperty, Mode=TwoWay}"/>
  • OneTime :数据在初始化时从绑定源流向绑定目标,之后绑定源的变化不会再影响绑定目标。适用于数据在应用程序运行过程中不会改变的情况,如显示应用程序版本号等。
<TextBlock Text="{Binding AppVersion, Mode=OneTime}"/>

(二)实现数据绑定

  1. 创建数据源 :通常会创建一个 ViewModel 类来作为数据源。ViewModel 类应该实现 INotifyPropertyChanged 接口,以便在属性值发生变化时通知绑定目标更新。例如:
public class UserViewModel : INotifyPropertyChanged
{
    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            _name = value;
            OnPropertyChanged(nameof(Name));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
  1. 在 XAML 中设置绑定 :在 XAML 文件中,将 UI 元素的属性绑定到 ViewModel 的属性上。假设在窗口的代码隐藏文件中创建了 UserViewModel 的实例,并将其设置为窗口的 DataContext
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        UserViewModel viewModel = new UserViewModel();
        DataContext = viewModel;
    }
}

在 XAML 中:

<TextBox Text="{Binding Name}"/>

这样, TextBoxText 属性就与 UserViewModel 中的 Name 属性绑定在一起了,当 Name 属性值改变时,

TextBox 中的文本会自动更新,反之,当用户在 TextBox 中输入新的文本时, Name 属性的值也会相应更新(如果绑定模式是 TwoWay )。

(三)绑定到集合

在实际应用中,经常需要将 UI 元素绑定到集合数据上,例如将 ListViewComboBox 绑定到一个 ObservableCollectionObservableCollection 是一个动态集合,当集合中的元素发生添加、删除或修改操作时,会自动通知绑定的 UI 元素进行更新。

以下是一个将 ListView 绑定到 ObservableCollection 的示例:

public class EmployeeViewModel
{
    public ObservableCollection<Employee> Employees { get; set; }

    public EmployeeViewModel()
    {
        Employees = new ObservableCollection<Employee>
        {
            new Employee { Name = "John Doe", Age = 30 },
            new Employee { Name = "Jane Smith", Age = 25 },
            new Employee { Name = "Bob Johnson", Age = 35 }
        };
    }
}

public class Employee
{
    public string Name { get; set; }
    public int Age { get; set; }
}

在 XAML 中:

<Window.Resources>
    <DataTemplate x:Key="EmployeeTemplate">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding Name}" Margin="5"/>
            <TextBlock Text="{Binding Age}" Margin="5"/>
        </StackPanel>
    </DataTemplate>
</Window.Resources>
<ListView ItemsSource="{Binding Employees}" ItemTemplate="{StaticResource EmployeeTemplate}"/>

在窗口的代码隐藏文件中设置 DataContext

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        EmployeeViewModel viewModel = new EmployeeViewModel();
        DataContext = viewModel;
    }
}

这样, ListView 会显示 Employees 集合中的每个 Employee 对象,并且使用 EmployeeTemplate 定义的模板来呈现每个对象的 NameAge 属性。

(四)数据绑定的其他特性

  1. 绑定转换器 :当绑定源和绑定目标的类型不匹配时,或者需要对绑定值进行一些转换时,可以使用绑定转换器。绑定转换器是实现了 IValueConverter 接口的类。例如,将一个布尔值转换为字符串显示:
public class BooleanToStringConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is bool boolValue)
        {
            return boolValue ? "Yes" : "No";
        }
        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is string stringValue)
        {
            return stringValue == "Yes";
        }
        return false;
    }
}

在 XAML 中使用转换器:

<Window.Resources>
    <local:BooleanToStringConverter x:Key="BooleanToStringConverter"/>
</Window.Resources>
<TextBlock Text="{Binding IsActive, Converter={StaticResource BooleanToStringConverter}}"/>
  1. 绑定验证 :在用户输入数据时,需要对输入的数据进行验证,确保数据的有效性。WPF 提供了绑定验证机制,可以通过实现 IDataErrorInfo 接口或使用 ValidationRule 类来实现验证。例如,验证一个文本框输入的是否为有效的整数:
public class IntegerValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        if (int.TryParse(value as string, out _))
        {
            return ValidationResult.ValidResult;
        }
        return new ValidationResult(false, "请输入有效的整数。");
    }
}

在 XAML 中应用验证规则:

<Window.Resources>
    <local:IntegerValidationRule x:Key="IntegerValidationRule"/>
</Window.Resources>
<TextBox>
    <TextBox.Text>
        <Binding Path="Age" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <local:IntegerValidationRule/>
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

当用户输入的不是有效的整数时,文本框会显示验证错误信息。

五、样式和模板

(一)样式基础

样式是一种用于集中设置 UI 元素属性的机制,可以将一组属性应用到多个元素上,实现统一的外观风格。样式可以定义在资源字典中,也可以直接在 XAML 文件中定义。

以下是一个简单的样式定义示例:

<Window.Resources>
    <Style x:Key="MyButtonStyle" TargetType="Button">
        <Setter Property="Background" Value="LightBlue"/>
        <Setter Property="Foreground" Value="White"/>
        <Setter Property="FontSize" Value="14"/>
    </Style>
</Window.Resources>
<Button Style="{StaticResource MyButtonStyle}" Content="Styled Button"/>

在这个示例中,定义了一个名为 MyButtonStyle 的样式,目标类型是 Button 。通过 Setter 元素设置了按钮的背景色、前景色和字体大小。然后将这个样式应用到一个按钮上。

(二)隐式样式

隐式样式是一种不指定 x:Key 的样式,它会自动应用到所有指定 TargetType 的元素上。例如:

<Window.Resources>
    <Style TargetType="Button">
        <Setter Property="Background" Value="Green"/>
        <Setter Property="Foreground" Value="White"/>
    </Style>
</Window.Resources>
<Button Content="Implicit Styled Button"/>

在这个示例中,定义了一个隐式样式,目标类型是 Button 。所有在该窗口中的按钮都会自动应用这个样式。

(三)模板基础

模板用于自定义 UI 元素的可视化结构。WPF 提供了两种主要的模板类型: ControlTemplateDataTemplate

  1. ControlTemplate :用于自定义控件的外观。例如,自定义一个按钮的模板:
<Window.Resources>
    <ControlTemplate x:Key="CustomButtonTemplate" TargetType="Button">
        <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
            <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
        </Border>
        <ControlTemplate.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Background" Value="LightGray"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>
</Window.Resources>
<Button Template="{StaticResource CustomButtonTemplate}" Content="Custom Button"/>

在这个示例中,定义了一个 ControlTemplate ,通过 Border 元素和 ContentPresenter 元素来构建按钮的外观。 TemplateBinding 用于将模板中的属性绑定到控件的实际属性上。 ControlTemplate.Triggers 用于定义触发条件,当鼠标悬停在按钮上时,改变按钮的背景色。

  1. DataTemplate :用于定义数据项的呈现方式。例如,将一个列表框中的数据项以自定义的方式显示:
<Window.Resources>
    <DataTemplate x:Key="PersonDataTemplate">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding Name}" Margin="5"/>
            <TextBlock Text="{Binding Age}" Margin="5"/>
        </StackPanel>
    </DataTemplate>
</Window.Resources>
<ListBox ItemsSource="{Binding Persons}" ItemTemplate="{StaticResource PersonDataTemplate}"/>

在这个示例中,定义了一个 DataTemplate ,用于显示 Persons 集合中的每个 Person 对象。 DataTemplate 中使用 StackPanel 水平排列 NameAge 属性的文本块。

(四)资源字典

资源字典是一种用于集中管理样式、模板、画笔等资源的机制。可以将资源字典定义在单独的 .xaml 文件中,然后在多个 XAML 文件中引用。

以下是一个资源字典文件 MyResources.xaml 的示例:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style x:Key="MyButtonStyle" TargetType="Button">
        <Setter Property="Background" Value="Orange"/>
        <Setter Property="Foreground" Value="White"/>
    </Style>
</ResourceDictionary>

在 XAML 文件中引用资源字典:

<Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="MyResources.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Window.Resources>
<Button Style="{StaticResource MyButtonStyle}" Content="Button from Resource Dictionary"/>

通过这种方式,可以实现资源的共享和复用,提高代码的可维护性。

六、动画和多媒体

(一)动画基础

WPF 提供了丰富的动画系统,允许对 UI 元素的属性进行动态变化,从而创建出各种生动的效果。动画主要分为线性动画、关键帧动画和路径动画。

  1. 线性动画 :线性动画是最简单的动画类型,它在指定的时间内从一个值线性变化到另一个值。例如,实现一个按钮在点击时逐渐变大的动画:
<Window.Resources>
    <Storyboard x:Key="GrowButtonAnimation">
        <DoubleAnimation Storyboard.TargetProperty="Width"
                         From="100" To="150" Duration="0:0:0.5"/>
        <DoubleAnimation Storyboard.TargetProperty="Height"
                         From="50" To="75" Duration="0:0:0.5"/>
    </Storyboard>
</Window.Resources>
<Button Content="Animate Me" Click="Button_Click">
    <Button.Triggers>
        <EventTrigger RoutedEvent="Button.Click">
            <BeginStoryboard Storyboard="{StaticResource GrowButtonAnimation}"/>
        </EventTrigger>
    </Button.Triggers>
</Button>

在这个示例中,定义了一个 Storyboard ,其中包含两个 DoubleAnimation ,分别对按钮的 WidthHeight 属性进行动画处理。当按钮被点击时,触发 EventTrigger ,开始播放动画。

  1. 关键帧动画 :关键帧动画允许在动画过程中定义多个关键帧,每个关键帧指定一个特定的时间点和属性值。例如,实现一个按钮在不同时间点改变颜色的动画:
<Window.Resources>
    <Storyboard x:Key="ColorAnimationStoryboard">
        <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Control.Background).(SolidColorBrush.Color)" Duration="0:0:2">
            <LinearColorKeyFrame Value="Red" KeyTime="0:0:0"/>
            <LinearColorKeyFrame Value="Green" KeyTime="0:0:1"/>
            <LinearColorKeyFrame Value="Blue" KeyTime="0:0:2"/>
        </ColorAnimationUsingKeyFrames>
    </Storyboard>
</Window.Resources>
<Button Content="Color Animate" Click="Button_Click_1">
    <Button.Triggers>
        <EventTrigger RoutedEvent="Button.Click">
            <BeginStoryboard Storyboard="{StaticResource ColorAnimationStoryboard}"/>
        </EventTrigger>
    </Button.Triggers>
</Button>

在这个示例中,使用 ColorAnimationUsingKeyFrames 定义了一个颜色动画,通过 LinearColorKeyFrame 指定了不同时间点的颜色值。

  1. 路径动画 :路径动画允许元素沿着指定的路径移动。例如,让一个椭圆沿着一个圆形路径移动:
<Window.Resources>
    <Storyboard x:Key="PathAnimationStoryboard">
        <PointAnimationUsingPath Storyboard.TargetProperty="(Canvas.LeftProperty)"
                                 Storyboard.TargetName="ellipse"
                                 Duration="0:0:5"
                                 RepeatBehavior="Forever">
            <PointAnimationUsingPath.PathGeometry>
                <EllipseGeometry Center="200,200" RadiusX="100" RadiusY="100"/>
            </PointAnimationUsingPath.PathGeometry>
        </PointAnimationUsingPath>
    </Storyboard>
</Window.Resources>
<Canvas>
    <Ellipse x:Name="ellipse" Width="20" Height="20" Fill="Red" Canvas.Left="100" Canvas.Top="100"/>
    <Button Content="Start Animation" Canvas.Left="10" Canvas.Top="10" Click="Button_Click_2">
        <Button.Triggers>
            <EventTrigger RoutedEvent="Button.Click">
                <BeginStoryboard Storyboard="{StaticResource PathAnimationStoryboard}"/>
            </EventTrigger>
        </Button.Triggers>
    </Button>
</Canvas>

在这个示例中,使用 PointAnimationUsingPath 让椭圆沿着一个圆形路径移动,路径由 EllipseGeometry 定义。

(二)多媒体支持

WPF 提供了对多媒体的支持,包括音频和视频的播放。可以使用 MediaElement 控件来播放多媒体文件。

以下是一个播放视频的示例:

<MediaElement Source="video.mp4" Width="640" Height="360"
              LoadedBehavior="Play" UnloadedBehavior="Stop"/>

在这个示例中, MediaElement 控件的 Source 属性指定了要播放的视频文件的路径。 LoadedBehavior 属性设置为 Play 表示当控件加载完成后自动播放视频, UnloadedBehavior 属性设置为 Stop 表示当控件卸载时停止播放视频。

七、命令系统

(一)命令基础

WPF 的命令系统提供了一种将用户操作(如按钮点击、菜单项选择等)与业务逻辑分离的机制。命令是一种抽象的操作,它定义了操作的执行逻辑和是否可以执行的判断逻辑。

(二)实现命令

WPF 中常用的命令实现方式是使用 RelayCommand 类,它是一个自定义的命令类,实现了 ICommand 接口。以下是一个 RelayCommand 类的实现:

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

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

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

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

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

在 ViewModel 中使用 RelayCommand

public class MainViewModel
{
    public RelayCommand MyCommand { get; set; }

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

    private void ExecuteMyCommand(object parameter)
    {
        // 执行命令的逻辑
        MessageBox.Show("Command executed!");
    }

    private bool CanExecuteMyCommand(object parameter)
    {
        // 判断命令是否可以执行的逻辑
        return true;
    }
}

在 XAML 中绑定命令:

<Button Content="Execute Command" Command="{Binding MyCommand}"/>

(三)系统命令

WPF 还提供了一些内置的系统命令,如 ApplicationCommands.NewApplicationCommands.OpenApplicationCommands.Save 等。这些命令可以直接在 XAML 中使用,例如:

<Menu>
    <MenuItem Header="File">
        <MenuItem Header="New" Command="{x:Static ApplicationCommands.New}"/>
        <MenuItem Header="Open" Command="{x:Static ApplicationCommands.Open}"/>
        <MenuItem Header="Save" Command="{x:Static ApplicationCommands.Save}"/>
    </MenuItem>
</Menu>

系统命令会自动处理一些常见的操作,如快捷键绑定、命令状态更新等。

https://i-blog.csdnimg.cn/direct/2d5bb260b23345aca1221e45131e57c2.png