WPF 入门基础

关于 WPF 和 XAML

什么是 WPF

WPF(Windows Presentation Foundation)是由微软开发的桌面应用程序框架,用于创建现代化、高度交互和具有视觉吸引力的用户界面。它是 .NET Framework 的一部分,提供了一种基于 XAML(Extensible Application Markup Language)语言的声明性编程模型,可以很容易地创建动态、灵活的用户界面,并且可以与其他 .NET 技术无缝集成。WPF 还具有强大的数据绑定和可重用性,使开发人员可以更快地构建和维护应用程序。WPF 也支持硬件加速和高分辨率显示,为用户带来更好的体验。

WPF 和 Winform 的区别

WPF(Windows Presentation Foundation)和 WinForms(Windows Forms)都是用于创建 Windows 桌面应用程序的框架,但它们有一些重要的区别:

  1. 编程模型:WPF 是基于 XAML 的声明性编程模型,它可以很容易地创建动态、灵活的用户界面,支持动画和高级视觉效果。而 WinForms 则是基于传统的命令式编程模型,需要在代码中手动设置每个控件的属性和事件处理程序。
  2. 数据绑定:WPF 有一个强大的数据绑定系统,可以将 UI 元素和数据源相互绑定,使应用程序更容易管理和更新数据。WinForms 也支持数据绑定,但不如 WPF 灵活。
  3. 可重用性:WPF 支持样式和模板,使 UI 元素可以轻松地重用和自定义,这大大简化了应用程序的开发和维护。WinForms 则需要手动创建每个 UI 元素,不太容易重用。
  4. 矢量图形和分辨率:WPF 使用矢量图形,可在高分辨率屏幕上呈现清晰的图像,而 WinForms 使用像素图形,可能在高分辨率屏幕上显示模糊或失真。

WinForms 与 WPF 间最大的差异在于 WinForms 只是单纯在 Windows 标准控制项 (例如:TextBox) 上叠一层,而 WPF 几乎是全面从零建构,并未依赖任何 Windows 标准控制项。这差异看起来很微妙,实则不然。如果你曾经使用过依赖 Win32/WinAPI 的框架,就一定会注意到这种差异。

关于这种差异有一个很好的例子就是一个带有图像和文本的按钮。这不是一个标准的 Windows 控件,所以 WinForms 没有为你提供开箱即用的这种可能性。相反,你必须自己绘制图像,实现自己的支持图像的按钮或使用第三方控件。使用WPF,按钮可以包含任何内容,因为它本质上是一个包含内容和各种状态(例如未动、悬停、按下)的边框。 WPF 按钮和大多数其他 WPF 控件一样是“无外观”的,这意味着它可以包含一系列其他控件。想要得到一个带有图像和一些文本的按钮只需要在按钮(Button)里放一个 Image 和一个 TextBlock 控件,就这样!但是你根本无法从 WinForms 默认控件中获得这种灵活性,这就是为什么控件的简单实现(如带图像的按钮等)有很大的市场。

总的来说,WPF 更适合创建现代化、高度交互和具有视觉吸引力的用户界面,而 WinForms 则更适合创建传统的 Windows 应用程序。

Hello WPF

新建一个 WPF 工程:

  • 默认情况下会创建两个文件,一个是 XAML 文件(mainwindow.xaml),另一个是 CS 文件(mainwindow.cs)

  • 在 mainwindow.xaml 上,可以看到两个子窗口,一个是设计窗口,另一个是(XAML)窗口

  • 在 WPF 应用程序中,有两种方法可以为你的应用程序设计 UI。一种是简单地将 UI 元素从工具箱拖放到设计窗口。第二种方法是通过为 UI 元素编写 XAML 标记来设计 UI。当拖放功能用于 UI 设计时,Visual Studio 会处理 XAML 标记。

  • 默认情况下,Grid 设置为页面后的第一个元素。

我们来创建一个TextBlock来显示著名的HelloWorld,两种方法:

<1> 编写 XAML 来声明

<Window x:Class="HelloWPF.MainWindow"
        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:HelloWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TextBlock  HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="72">
            Hello WPF
        </TextBlock>
    </Grid>
</Window>

image-20230325113534145

我们使用了三个不同的属性来获得一个自定义的窗口中央位置,FontSize属性获的更大的字体。

<2> 从工具箱拖放控件到设计器

#<Window x:Class="HelloWPF.MainWindow"
        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:HelloWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TextBlock x:Name="textBlock" HorizontalAlignment="Center" TextWrapping="Wrap" 
                   Text="Hello WPF" VerticalAlignment="Center" Height="25" Width="80"/>
    </Grid>
</Window>

image-20230325113501524

从工具箱拖入的控件,在XMAL中的属性就比较详细了。

什么是XAML

使用 WPF 时首先遇到的事情之一是 XAML。XAML 代表可扩展应用程序标记语言,一种基于 XML 的简单的声明性语言,是微软用于描述 GUI 的 XML 变种。在之前的 GUI 框架如 WinForms 中,GUI 是用相同语言创建,例如 C# 或 VB.NET ,并且通常由设计者来维护。但是,通过 XAML,微软使用了另一种方式。非常类似HTML,你现在能轻松编写你的GUI。

  • 在 XAML 中,很容易创建、初始化和设置具有层次关系的对象的属性。
  • 它主要用于设计 GUI,但也可以用于其他目的,例如在 Workflow Foundation 中声明工作流。

创建 WPF 项目时,在MainWindow.xaml中会默认生成一些XAML代码:

<Window x:Class="HelloWPF.MainWindow"
        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:HelloWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        
    </Grid>
</Window>
信息 描述
<Window 它是根的开放对象元素或容器。
x:Class = "HelloWPF.MainWindow" 它是一个部分类声明,将标记连接到后面定义的部分类代码。
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 映射 WPF 客户端/框架的默认 XAML 命名空间
xmlns:x = "http://schemas.microsoft.com/w infx/2006/xaml" XAML 语言的 XAML 命名空间,将其映射到 x: 前缀
> 根的对象元素的结尾
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d: XAML命名空间在XAML元素上启用设计器属性。这些设计器属性只影响XAML行为的设计方面。当XAML解析器在Silverlight运行时加载相同的XAML并且应用程序运行时,将忽略设计器属性。通常,设计器属性对任何XAML元素都有效,但实际上只有在某些情况下才适合自己应用设计器属性。
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc: 表示并支持阅读XAML的标记兼容模式。通常,d:前缀与属性 mc:Ignorable 相关联。此技术使运行时XAML分析器能够忽略设计属性

它是空Grid对象的开始和结束标记。
关闭对象元素

命名空间的解释是我在 Stackoverflow 上找到的。

XAML 的语法规则与 XML 几乎相似。如果查看 XAML 文档,您会注意到它实际上是一个有效的 XML 文件,但 XML 文件不一定是 XAML 文件。这是因为在 XML 中,属性的值必须是字符串,而在 XAML 中,它可以是不同的对象,称为 Property 元素语法。

XAML 基础

​ XAML标签必须有结尾, 在起始标签尾部用斜杠或者使用结束标签都可以。

<Button></Button>

或者

<Button />

多数的控件允许你在开始和结束之间放置内容, 这就是控件的内容( content ). 比如,Button控件允许你在开始和结束之间指定显示的文字,这看起来是不是和 HTML 页面标签一样:

<Button>A button</Button>

但需要注意的是:

HTML不区分大小写,但 XAML 区分。 因为控件的名字最终会有对应到 .NET 框架下的类型 (Type) ,同理 XAML 标签的属性也区分大小写,因为这是控件的属性。

我们可以定义一个带有属性的Button控件:

image-20230325130833830

<Grid>
    <Button FontWeight="Bold" Content="A button"/>
</Grid>

上面的 Buttoon 中我们设定了属性粗细 (FontWeight) 为粗体 (Bold) ,设定了内容 (Content) , 这和在 Button 控件标签的开始和结束间留下文字是一样的. 另外,我们还可以通过用标签来指定控件的属性内容, 标签名为点号连接控件名和属性名:

    <Grid>
        <!--<Button FontWeight="Bold" Content="A button"/>-->
        <Button>
            <Button.FontWeight>Bold</Button.FontWeight>
            <Button.Content>A buttoon</Button.Content>
        </Button>
    </Grid>

这两种写法效果是一样的,只是形式存在差异,很多的控件允许使用文字以外的作为内容,比如, 我们可以在按钮中嵌套TextBlock控件来显示3中不同颜色的文字:

<Grid>
    <!--<Button FontWeight="Bold" Content="A button"/>-->
    <Button>
        <Button.FontWeight>Bold</Button.FontWeight>
        <Button.Content>
            <WrapPanel>
                <TextBlock Foreground="LightGreen">Multi</TextBlock>
                <TextBlock Foreground="LightPink">Color</TextBlock>
                <TextBlock>Button</TextBlock>
            </WrapPanel>
        </Button.Content>
    </Button>
</Grid>

image-20230325131823199

内容 Content 属性只接受一个元素,所以我们用 WrapPanel 控件把三个 TextBlock 控件包起来了。

这样的Panel控件有很多的用途。

也可以省略简化成:

<Button FontWeight="Bold">
    <WrapPanel>
        <TextBlock Foreground="Blue">Multi</TextBlock>
        <TextBlock Foreground="Red">Color</TextBlock>
        <TextBlock>Button</TextBlock>
    </WrapPanel>
</Button>

代码 VS XAML

可以看出XAML还是很好写的,但是这并不唯一,不是说用 WPF 就必须这样,同样的效果同 C#写的话就是:

Button btn = new Button();
btn.FontWeight = FontWeights.Bold;

WrapPanel pnl = new WrapPanel();

TextBlock txt = new TextBlock();
txt.Text = "Multi";
txt.Foreground = Brushes.Blue;
pnl.Children.Add(txt);

txt = new TextBlock();
txt.Text = "Color";
txt.Foreground = Brushes.Red;
pnl.Children.Add(txt);

txt = new TextBlock();
txt.Text = "Button";
pnl.Children.Add(txt);

btn.Content = pnl;
pnlMain.Children.Add(btn);

从上述内容中我们需要注意以下两个鲜为人知的事实:

  • WPF 不需要 XAML
  • XAML 不需要 WPF

它们是可分离的技术,很明显在 XAML 中创建、初始化和设置对象的属性的任务也可以使用代码完成。

  • XAML 只是另一种设计 UI 元素的简单方法。
  • 使用 XAML,这并不意味着您可以设计 UI 元素是唯一的方法。您可以在 XAML 中声明对象或使用代码定义它们。
  • XAML 是可选的,但尽管如此,它是 WPF 设计的核心。
  • XAML 的目标是使可视化设计人员能够直接创建用户界面元素。
  • WPF 旨在使从标记控制用户界面的所有视觉方面成为可能。

元素树

什么是元素树

元素树是一种树形数据结构,通常用于表示文档对象模型(DOM)中的 HTML 或 XML 文档。在元素树中,每个 HTML 或 XML 元素都表示为一个节点,而元素之间的嵌套关系表示为树的父子关系。

在元素树中,根节点代表整个文档,而子节点代表文档中的每个元素。每个节点都包含了元素的标签名称、属性和文本内容等信息。通过遍历元素树,可以轻松地访问和修改文档的内容和结构。

WPF - 元素树

在 WPF 中,元素树是一种关键的概念,它代表了界面中所有元素的层次结构。

在 WPF 中,所有的可视元素(如控件、面板、布局等)都继承自FrameworkElementFrameworkContentElement类,这些类都是 WPF 中元素树的一部分。元素树中的每个元素都可以包含子元素,这些子元素可以是其他可视元素,也可以是非可视元素(如绑定对象、转换器等)。

WPF 中的元素树与 HTML 或 XML 文档的元素树类似,都是一种树形数据结构,用于表示一个文档或应用程序中的元素之间的层次结构关系。通过遍历 WPF 中的元素树,我们可以轻松地访问和修改界面中的元素,实现复杂的布局、数据绑定和事件处理等功能,为开发人员提供了一种方便、灵活的方式来构建和管理复杂的用户界面。

在 WPF 中,有两种方法可以概念化完整的对象树 -

  • 逻辑树结构 ( XAML 部分)
  • 可视化树结构 ( 渲染到输出屏幕的所有 UI 元素 )

程序运行时,可以在 Live Visual Tree 窗口中看到正在运行的应用程序的可视化树,该窗口显示了该应用程序的完整层次结构。

image-20230325134150128

WPF - 依赖属性

在WPF框架中,依赖属性是一种特殊类型的属性,是扩展 CLR 属性的特定类型的属性,它允许元素继承和共享属性值,并支持数据绑定、样式、动画和值转换等高级功能。

依赖属性的一个重要特点是它们具有优先级顺序,可以根据优先级顺序从多个来源(例如本地值、样式、模板、继承值、动画等)中获取最终值。这使得元素可以根据自己的需求来重写或继承属性的值,从而实现更灵活、可扩展的界面设计。

另一个重要的特点是依赖属性支持数据绑定,这意味着它们可以与其他元素或数据源进行双向或单向绑定,使得界面与数据之间的交互更加自然和动态。

在 WPF 中,许多常见的属性( 如Width、Height、Background等 )都是依赖属性。开发人员还可以定义自己的依赖属性,以扩展WPF中的元素和功能。

定义依赖属性的类必须继承自 DependencyObject 类。 XAML 中使用的许多 UI 控件类都派生自 DependencyObject 类,它们支持依赖属性。

例如 Button 类支持 IsMouseOver 依赖属性:

<Window x:Class="HelloWPF.MainWindow"
        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:HelloWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Button Height="40" Width="175" Margin="10" Content="Dependency Property">
            <Button.Style>
                <Style TargetType="{x:Type Button}">
                    <Style.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Foreground" Value="Red"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </Button.Style>
        </Button>
    </Grid>
</Window>

这是一段关于悬停效果的XAML:当鼠标悬停在按钮上时,按钮的前景色会变为红色。当鼠标离开按钮时,它会变回原来的颜色。XAML 中的x:Type标记扩展具有与 C# 中的 typeof() 类似的功能。它用于指定采用对象类型的属性。

为什么需要依赖属性

当在应用程序中使用依赖属性时,它会带来各种好处。在以下情况下,依赖属性可以在 CLR 属性上使用 -

  • 如果要设置样式
  • 如果你想要数据绑定
  • 如果要设置资源(静态或动态资源)
  • 如果你想支持动画

基本上,依赖属性提供了许多使用 CLR 属性无法获得的功能。

依赖属性 和其他 CLR属性 的主要区别如下-

  • CLR 属性可以通过使用 gettersetter 直接从类的私有成员中读取/写入。相反,依赖属性不存储在本地对象中。
  • 依赖属性存储在DependencyObject类提供的键/值对字典中。它还节省了大量内存,因为它在更改时存储了属性。也可以绑定在 XAML 中。

XAML 中的事件

现在多数的 UI 框架都是由事件驱动的,WPF 也是。所有的控件都提供了大量的事件可以订阅,这意味着,你的程序将在事件发生时接受到通知并且你可以对这些事件做出相应。事件有很多不同的类型,大部分会发生在用户鼠标键盘和程序互动的时候,例如KeyDown, KeyUp, MouseDown, MouseEnter, MouseLeave, MouseUp等类的事件,事件的工作原理描述起来很复杂,我们现在只需要知道怎样连接XAML和代码程序就可以了:

<Window x:Class="HelloWPF.MainWindow"
        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:HelloWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid Name="pnlMainGrid" MouseUp="pnlMainGrid_MouseUp" Background="LightBlue">
        
    </Grid>
</Window>

我们在 Grid 中订阅了MouseUp事件, 指向方法pnlMainGrid_MouseUp,我们在方法中添加一个MessageBox来查看效果:

private void pnlMainGrid_MouseUp(object sender, MouseButtonEventArgs e)
{
    MessageBox.Show("You clicked me at " + e.GetPosition(this).ToString());
}

image-20230325160109119

MouseUp事件使用的是一个名为MouseButtonEventHandler的委托,它有两个参数, sender(发生事件的控件), MouseButtonEventArgs(一些有用的信息, 我们在上面的例子中通过它获取了鼠标的位置)。

有些事件会使用同一个委托,比如MouseUpMouseDown共用MouseButtonEventHandler, 注意MouseMove用的是MouseEventHandler。 当你定义事件处理方法的时候, 你需要知道事件使用的委托, 文档里可以找到。

小tip:Visual Studio 可以帮助我们生成正确的事件处理方法。 在XAML中输入事件的名字, VS 的 IntelliSense 就会提供生成新事件处理方法。

当然,也可以通过程序订阅事件:在 C# 中这会使用到 += 语法,做过 winform 开发的应该很熟悉了。

WPF - 路由事件

XAML 中的事件和 WPF 中的路由事件是不同的,尽管它们有一些相似之处。XAML 中的事件是传统的事件模型,即一个元素上发生的事件会通知该元素的事件处理程序。事件处理程序可以是直接在 XAML 中声明的,也可以是在代码中编写的。

WPF 中的路由事件是一种更复杂的事件模型,它可以在元素树中的多个侦听器上调用处理程序,而不仅仅是引发事件的对象。它基本上是一个由 Routed Event 类的实例支持的 CLR 事件。它在 WPF 事件系统中注册。RoutedEvents 具有三种主要的路由策略,如下所示 -

  • 直接事件
  • 冒泡事件
  • 隧道事件

可以通过整个元素树进行事件冒泡或隧道传递。这意味着当元素上发生路由事件时,事件会向上传递到其父元素,直到到达顶层元素,然后再向下传递到具有匹配事件处理程序的元素。路由事件使得可以更灵活地处理事件,并且可以减少事件处理程序的数量。

虽然路由事件是 WPF 中的一个特性,但它们并不是 XAML 的一部分。XAML 中可以使用传统的事件模型或路由事件模型,具体取决于所使用的技术和需要。

WPF 应用程序

创建 WPF 应用程序时,你首先会遇到Window类。它作为窗体的根节点,提供了标准的边框,标题栏和最大化,最小化和关闭按钮。WPF 窗体是 XAML(.xaml)文件(其中元素是根)和后台代码(.cs)文件的组合。如果正在使用 Visual Studio 并创建一个新的WPF应用程序,它将创建一个默认窗体,如下所示:

<Window x:Class="WpfApplication1.Window"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="300" Width="300">
    <Grid>

    </Grid>
</Window>

x:Class属性告诉 XAML 文件使用哪个类,实例中的是Windows1,它是由Visual Studio 默认创建的,我们可以在MainWindow.xaml.cs中找到他:

namespace HelloWPF
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
           
        }
    }
}

正如我们所看到的,Class: Windowpartial修饰,因为它在运行时与XAML文件相结合,为您提供完整的窗口。这实际上就是对InitializeComponent()的调用所做的,这就是为什么需要它来启动并运行一个完整的窗口。如果我们返回到XAML文件,你会注意到Window元素上有一些其他有趣的属性,例如Title,它定义了窗口的标题(显示在标题栏中)还有起始宽度和高度。还有一些命名空间定义,我们将在XAML章节中讨论。

我们还会注意到 Visual Studio 已在 Window 中为我们创建了一个 Grid 控件。Grid 是 WPF 面板之一。虽然这个被包含在 Window 中的控件也可以是任何面板或控件,但 Window 只能拥有一个子控件 ,因此,使用一个可以包含多个子控件的面板通常是一个不错的选择。

重要的Window属性

WPF 的 Window 类有许多有趣的属性,您可以设置这些属性来控制应用程序窗口的外观和行为。

Icon - 允许你定义窗口的图标,该图标通常显示在窗口标题之前的左上角。

ResizeMode - 这可以控制最终用户是否以及如何调整窗口大小。默认是CanResize,允许用户像任何其他窗口一样调整窗口大小,使用最大化/最小化按钮或拖动其中一个边缘。CanMinimize将允许用户最小化窗口,但不能最大化它或拖动它更大或更小。NoResize是最严格的,最大化和最小化按钮被移除,窗口不能被拖得更大或更小。

ShowInTaskbar - 默认值为true,但如果将其设置为false,则窗口将不会在Windows任务栏中显示。适用于非主窗口或应尽量减少托盘的应用程序。

Make correction

SizeToContent - 决定Window是否应调整自身大小以自动适应其内容。默认是Manual, 这意味着窗口不会自动调整大小。其他选项有Width,Height和WidthAndHeight,分别对应自动调整宽度,高度或同时调整两者。

Topmost - 默认是false, 但如果设置为true,除非最小化,否则您的窗口将保持在其他窗口之上。仅适用于特殊情况。

WindowStartupLocation - 控制窗口的初始位置。默认是Manual, 表示窗口最初将根据窗口的Top和Left属性进行定位。其他选项是CenterOwner,它将窗口定位在其所有者窗口的中心,以及CenterScreen,它将窗口定位在屏幕的中心。

WindowState - 控制初始窗口状态。它可以是Normal,Maximized或Minimized。默认值为Normal,除非您希望窗口最大化或最小化,否则应该使用它。

当然还有很多其他属性,请自行查阅。

屏幕看着眼疼🌌

posted @ 2023-03-25 17:32  PixelKiwi  阅读(7278)  评论(0编辑  收藏  举报