WPF基础知识
引言
因为博主在岗位是客户端开发,所以在接触到WPF框架的使用感触最深就是感觉这和前端没什么区别,只是传统前端大部分是Web开发,而是进行桌面程序开发。由于博主岗位的原因,博主理解的WPF框架技术和以下介绍的技术也是基于b博主在岗位中学习的收获理解来继续的分享。
博主在岗位中对WPF客户端的认识,WPF主要分为前端页面部分(.xaml)和后端逻辑部分(.cs),接下来也将以这两个方面讲解。
WPF概述
WPF (Windows Presentation Foundation)是微软开发的用于构建现代Windows应用程序的框架。它是.NET Framework中的一个重要组件,通过提供丰富的界面设计、图形渲染、数据绑定、布局、动画和多媒体等功能,使开发人员能够创建具有高度交互性和可视化效果的应用程序。
前端页面部分(*.xaml)
页面展示
页面部分主要分为三大类,分别是窗口、页面、数据字典。
窗口
窗口即弹出来的窗体,以WIndow为关键字。在开发中主要是在客户端的整个窗体和弹窗提示会用到,以下是大体框架。
<Window x:Class="myClass">
<Grid>
</Grid>
</Window>
页面
页面就是嵌入窗体的内容,如同打开的一个浏览器里面开多个页面,以Page为关键字。在博主的日常开发中页面的使用频率会比窗体高很多,主要产品就是一个窗口里面很短选项,可以打开很多的页面,一下是大体框架。
<Page x:Class="myClass">
<Grid>
</Grid>
</Page>
资源字典
资源字典是将页面内容分为多个模块,每个单独模块内容用一个资源字典封装,也能让主页面部分代码看着结构更加清晰明了。博主在实际开发中遇到的大部分页面都是分为多个模块,由页面调用各个模块的资源字典,将各个模块内的具体内容写入各个资源字典中,也有利于后续的维护。以下是大致框架。
<ResourceDictionary x:Class="myClass">
<ResourceDictionary.MergedDictionaries>
<core:ShareResourceDictionary Source="。。。"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="{x:Type local:。。。}">
</Style>
</ResourceDictionary>
资源字典除了可以放在单独文件内,也可以直接嵌入页面内使用,一下是大致框架。
<Page x:Class="myClass">
<ResourceDictionary x:Class="myClass">
<ResourceDictionary.MergedDictionaries>
<core:ShareResourceDictionary Source="。。。"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="{x:Type local:。。。}">
</Style>
</ResourceDictionary>
<Grid>
</Grid>
</Page>
绑定方式
WPF 中的数据绑定可以分为三种类型:静态绑定 (Static Binding)、动态绑定(Dynamic Binding)和绑定(Binding)
静态绑定 (Static Binding)
静态绑定通常指的是在 XAML 中直接使用静态资源或静态属性进行绑定。这种绑定不依赖于动态数据上下文,而是直接引用在编译时已知的静态值。静态绑定通常用于那些不需要改变的数据,例如固定的字符串或资源引用。
<Window.Resources>
<sys:String x:Key="MyStaticResource">这是一个静态资源</sys:String>
</Window.Resources>
<TextBlock Text="{StaticResource MyStaticResource}" />
在这个例子中,TextBlock 的 Text 属性通过静态资源进行静态绑定。
动态资源(Dynamic Binding)
动态绑定是指在运行时根据数据上下文(DataContext)的变化动态更新绑定的源和目标。这种绑定方式通常用于绑定到数据模型的属性,当数据模型的属性值发生变化时,UI 元素的显示也会相应地更新。
<Window.DataContext>
<local:MyViewModel />
</Window.DataContext>
<TextBlock Text="{Binding MyProperty}" />
在这个例子中,TextBlock 的 Text 属性通过 Binding 与 MyViewModel 类的 MyProperty 属性进行动态绑定。
绑定(Binding)
Binding 是 WPF 中实现数据绑定的核心类。它提供了一种机制,允许将 UI 元素的属性与数据源的属性进行关联。Binding 类提供了丰富的特性,如路径选择器、值转换器(IValueConverter)、多值绑定、相对源绑定等。
<TextBlock>
<Binding Path="MyProperty" Converter="{StaticResource MyValueConverter}" />
</TextBlock>
在这个例子中,Binding 元素用于创建一个绑定,它指定了要绑定的属性路径 MyProperty,并应用了一个值转换器 MyValueConverter。
Generic.xaml注册
Generic.xaml 是一个特殊的资源字典文件,它用于定义和注册通用的 XAML 资源。这些资源可以是样式、模板、控件、值转换器等,它们可以被应用程序中的多个控件或组件重复使用。
一下是Generic.xaml 文件的一些特点:
1.注册资源
Generic.xaml 允许你定义一组可以在应用程序中重复使用的资源。这些资源在应用程序启动时被加载,并且可以在 XAML 中通过 StaticResource 或 DynamicResource 关键字引用。
2.主题一致性
通过在不同的主题文件夹中放置 Generic.xaml 文件,你可以为不同的系统主题定义一致的资源集。这样,应用程序的外观和感觉可以自动适应用户的系统设置。
3.控件样式和模板
Generic.xaml 通常用于定义控件的默认样式和模板。例如,你可以为 Button、TextBox、ListBox 等控件定义一组通用的样式和模板,这些样式和模板可以被应用程序中的所有相应控件使用。
4.合并字典
Generic.xaml 可以与其他资源字典文件一起使用,通过 MergedDictionaries 元素在应用程序的 App.xaml 文件中进行合并。这样,你可以轻松地添加或替换应用程序的资源。
5性能优化
通过在 Generic.xaml 中定义资源,可以避免在多个地方重复定义相同的样式和模板,从而减少应用程序的内存占用和提高性能。
以下是一个简单的 Generic.xaml 文件示例:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="ControlBackgroundBrush" Color="White" />
<Style x:Key="DefaultButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Background" Value="{StaticResource ControlBackgroundBrush}" />
<!-- 其他 Setter 元素 -->
</Style>
<!-- 其他资源定义 -->
</ResourceDictionary>
在这个示例中,我们定义了一个 ControlBackgroundBrush 画刷资源和一个 DefaultButtonStyle 样式资源。这些资源可以在应用程序中的任何地方通过其键值引用。
除了可以直接在Generic.xaml里面注册样式,还可以在Generic.xaml里面注册一个资源字典,具体样式由资源字典内进行控制,以下是文件示例:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,/;Component/YourResourceDictionary.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
在这个示例中,我们注册了一个YourResourceDictionary.xaml的资源字典,具体的样式可以写在这个资源字典内,将每个样式封装起来,使得Generic.xaml文件内更加简洁,方便更好的找到我们需要的样式文件。
后台逻辑部分(*.cs)
面向对象三大基本特性
面向对象三大基本特性分别有封装、继承、多态,它们共同构成了大多数现代编程语言中类和对象行为的基础。封装、继承和多态一起提供了强大的机制来构建可重用、可扩展和可维护的代码。通过这些特性,可以创建模块化和层次化的应用程序,使得代码更加清晰和易于管理。
封装 (Encapsulation)
封装是指将数据(属性)和行为(方法)捆绑到一起,并对外界隐藏对象的实现细节。在C#中,通常通过使用访问修饰符(如 private、protected 和 public)来控制对类成员的访问,从而实现封装。
public class Car
{
private string _make;
private double _topSpeed;
public Car(string make, double topSpeed)
{
_make = make;
_topSpeed = topSpeed;
}
public string Make
{
get { return _make; }
}
public double TopSpeed
{
get { return _topSpeed; }
private set { _topSpeed = value; } // 仅允许私有方法修改
}
public void Drive()
{
// 行为的实现
}
}
在这个例子中,Car 类封装了汽车的制造商和最高速度信息,并通过公共属性和方法提供了对这些信息的访问和操作。
继承(Inheritance)
继承允许一个类(子类或派生类)继承另一个类(基类或父类)的属性和方法。子类可以添加新的成员或重写继承的成员,从而扩展或修改父类的行为。
public class ElectricCar : Car // 继承 Car 类
{
private int _batteryLevel;
public ElectricCar(string make, double topSpeed, int batteryLevel)
: base(make, topSpeed) // 调用基类的构造函数
{
_batteryLevel = batteryLevel;
}
public int BatteryLevel
{
get { return _batteryLevel; }
set { _batteryLevel = value; }
}
public void Charge()
{
// 充电行为的实现
}
}
在这个例子中,ElectricCar 类继承了 Car 类,并添加了新的属性和方法来描述电动汽车的特性。
多态(Polymorphism)
多态是指允许子类重写父类的方法,从而实现不同的行为。在C#中,多态通常通过抽象类、虚方法和接口来实现。
public abstract class Vehicle
{
public abstract void Drive();
}
public class Car : Vehicle
{
public override void Drive()
{
// 汽车驾驶行为的实现
}
}
在这个例子中,Vehicle 类是一个抽象类,它定义了一个抽象方法 Drive()。Car 类继承了 Vehicle 类,并提供了 Drive() 方法的具体实现。这样,我们可以在运行时根据对象的实际类型调用相应的 Drive() 方法。
重写和重载
重写(Overriding)和重载(Overloading)是面向对象编程中的两个重要概念,它们都与方法的实现有关,但它们的含义和用途有所不同。重写允许子类改变继承自基类的方法行为,而重载允许你根据传入的参数调用类中的不同方法。这两个概念都是多态性的重要组成部分,它们使得面向对象编程更加灵活和强大。
重写 (Overriding)
重写是指在派生类(子类)中修改或扩展基类(父类)中已定义方法的行为。在C#中,这是通过使用 override 关键字来实现的。重写允许子类根据需要提供特定于其自身的方法实现。
public class BaseClass
{
public virtual void Display()
{
Console.WriteLine("Display method of BaseClass");
}
}
public class DerivedClass : BaseClass
{
public override void Display()
{
base.Display();
Console.WriteLine("The display method is a DerivedClass within the BaseClass");
}
}
public class RewriteClass : BaseClass
{
public override void Display()
{
Console.WriteLine("Display method of RewriteClass");
}
}
在这个例子中,DerivedClass 继承并扩展了 BaseClass 的 Display 方法。当创建 DerivedClass 的对象并调用 Display 方法时,将先执行BaseClass中的版本,再执行 DerivedClass 中的版本。RewriteClass 重写了 BaseClass 的 Display 方法。当创建 RewriteClass 的对象并调用 Display 方法时,将执行 RewriteClass 中的版本。
重写通常与 virtual 和 override 关键字一起使用。基类中的方法需要被标记为 virtual,以便可以在派生类中被重写。在派生类中的重写方法里面可以通过 base. 的方法来调用父类中的该方法。
重载(Overloading)
重载是指在同一个类中定义多个具有相同名称但参数列表不同的方法。这是通过提供不同的参数类型、数量或顺序来实现的。重载允许你根据传入的参数调用不同的方法实现。
public class MyClass
{
public void Display(int value)
{
Console.WriteLine($"Value: {value}");
}
public void Display(string text)
{
Console.WriteLine($"Text: {text}");
}
}
在这个例子中,MyClass 有两个 Display 方法,它们的参数类型不同。根据传递给 Display 方法的参数,将调用相应的方法实现。
请注意,重载与方法的返回类型无关。即使两个方法的返回类型不同,只要它们的参数列表不同,它们也可以被重载。
抽象类与接口
抽象类和接口都是面向对象编程中用来实现抽象的机制,但它们在设计和使用上有一些关键的区别。抽象类作为基类使用,不能被实例化,可以包含部分实现代码。接口不包含实现代码,定义了类或结构体必须实现的方法规范。
抽象类(Abstract Class)
1.抽象类是一种特殊的类,它可以包含抽象方法(没有实现的方法)和具体方法(有实现的方法)。
2.抽象类不能被实例化,但它可以被继承,派生类必须实现抽象类中的所有抽象方法。
3.抽象类可以有构造函数、析构函数和字段。
4.抽象类可以提供方法的默认实现,派生类可以继承或重写这些实现。
5.抽象类使用 abstract 关键字声明。
public abstract class Animal
{
public abstract void MakeSound();
protected virtual void Eat() { /* ... */ }
}
接口
1.接口定义了一组没有实现的方法规范,它不包含任何实现代码。
2.接口可以被任何类或结构体实现,实现接口的类型必须提供接口中所有方法的具体实现。
3.接口不能包含字段或构造函数(C# 8.0 引入了接口中的默认接口方法,但这些方法仍然没有状态)。
4.接口使用 interface 关键字声明,并且接口中的所有成员默认是 public。
5.一个类可以实现多个接口。
public interface IShape
{
double Area();
double Perimeter();
}
区别
1.实现方式: 抽象类可以提供方法的默认实现,而接口不能包含任何实现代码。
2.成员类型: 抽象类可以包含字段、构造函数、析构函数等,而接口只能包含方法、属性、索引器和事件的声明。
3.继承与实现: 一个类只能继承一个抽象类(因为多重继承在C#中不被允许),但一个类可以实现多个接口。
4.访问修饰符: 接口中的成员默认是 public,不能指定其他访问修饰符。抽象类中的成员可以有不同的访问修饰符。
5.版本控制: 接口对版本控制比较敏感,添加新方法会破坏现有实现。抽象类可以通过添加非抽象方法来更容易地进行版本控制,而不会破坏现有派生类。
使用场景
1.如果你需要定义一个基类,它提供了一些共同的实现,并且希望派生类继承这些实现,那么抽象类是一个好选择。
2.如果你想要定义一个契约,强制多个不相关的类实现一组方法,那么接口是更好的选择。
3.如果你想要在类中实现多重类型契约,那么接口是唯一可行的选项,因为类可以实现多个接口。
4.在设计类层次结构时,合理使用抽象类和接口可以帮助你创建出更加灵活、可扩展和易于维护的代码。
常用访问修饰符
在面向对象编程中常见访问修饰符有public、private、protected 和 internal ,它们用于控制类成员(字段、方法、属性等)的访问级别。这些访问修饰符在C#中定义了成员可以被访问的范围。这些访问修饰符提供了不同级别的封装和控制,允许你根据需要设计类的封装性和可访问性。在设计类和成员时,应该仔细考虑每个成员的访问级别,以确保代码的安全性、可维护性和灵活性。通常,你应该尽量使用最严格的访问级别,除非有充分的理由需要更宽松的访问权限。
public(公共)
public 成员可以在任何地方被访问,无论访问者是否为成员的类型定义所在的程序集(Assembly)。
这是最宽松的访问级别,通常用于需要公开暴露的接口和功能。
public class MyClass
{
public int PublicField;
public void PublicMethod()
{
// ...
}
}
priavte(私有)
private 成员只能在定义它的类内部被访问。
这是最严格的访问级别,通常用于封装类的内部状态和实现细节。
public class MyClass
{
private int PrivateField;
private void PrivateMethod()
{
// ...
}
}
protected(保护)
protected 成员可以在定义它的类、派生类以及同一个程序集中的其他类中被访问。
这种访问级别适用于你希望基类和派生类能够访问的成员,但不希望外部类型访问。
public class MyBaseClass
{
protected int ProtectedField;
protected void ProtectedMethod()
{
// ...
}
}
internal(内部)
internal 成员只能在定义它的程序集内部被访问。
这种访问级别适用于你希望在当前程序集内部使用,但不希望在程序集外部暴露的成员。
internal class InternalClass
{
internal int InternalField;
internal void InternalMethod()
{
// ...
}
}
引用
基于WPF的开发的知识点
C#基础知识
面向对象编程三大特性------封装、继承、多态
WPF控件分类
理解WPF中的视觉树和逻辑树