当前位置: 首页 > 工具软件 > Avalonia > 使用案例 >

初识Avalonia

韩彦君
2023-12-01

Avalonia学习

基本知识点

代码项目结构

这是典型的MVVM模式结构,主要代码文件及目录用途如下:
Program.cs:系统入口
App.axaml:应用样式及初始化等
ViewLocator:View层与ViewModel层的映射

1.模型-视图-视图模型(MVVM)模式在Avalonia中创建一个简单的TODO应用程序
2.MVVM是用于编写GUl应用程序的常见模式,也是编写Avalonia应用程序时推荐使用的模式。这里我们将假设一个CRUD应用程序,但这些概念中的大多数都可以应用于所有类型的应用程序。在本指南中,我们将使用ReactiveUl,这是一个基于。net Reactive的MVVM框架扩展。本指南将解释如何使用MVVM和ReactiveUl与Avalonia,但你也可以看到ReactiveUl文档获取更详细的信息

3.创建一个mvvm的项目
你可以看到MVVM模式中的每个概念(模型、视图和视图模型)以及其他一些文件和目录
Assets目录保存应用程序的二进制资产,如图标和位图。放置在此目录中的文件将自动作为资源包含在应用程序中。
Models目录目前是空的,但顾名思义,这是我们将模型放置的地方。
ViewModels目录预先填充了视图模型的基类和应用程序主窗口的视图模型。
Views目录目前只包含应用程序主窗口。
App.axaml文件是放置应用于整个应用程序的XAML样式和模板的地方。
Program.cs文件是执行应用程序的入口点。在这里,如果需要,您可以为Avalonia配置平台选项。
ViewLocator.cs文件用于查找视图模型的视图。
4.在views文件夹中创建一个用户控件
这一切意味着什么?让我们逐行查看刚刚输入的代码。

<UserControl xmlns="https://github.com/avaloniaui"
             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">
             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
             x:Class="ToDoList.Views.UcToDoList">

XAML中的根元素是UserControl。后面是一堆xnlns声明。每一个都声明一个XML名称空间
但最重要的是第一个:xmlns="https://github.com/avaloniaui "。没有这个条目,什么都不会起作用。
下一个XML名称空间是xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml "。这用于导入不是Avalonia特有的XAML特性。我们将在后面看到它的使用。
x:Class=“ToDoList.Views.UcToDoList”。这一行告诉XAML引擎在哪里可以找到伴随XAML的类。它是类的完全限定名。注意,这个属性的前缀是x: -这与我们之前看到的xnins:x条目有关。好了,这就是样板文件!

首先我们添加一个DockPanel作为UserControl的子控件。一个UserControl只能包含一个孩子,所以孩子通常是Avalonia的面板控件之一。面板控件的特殊之处在于它们可以有多个子控件。DockPanel是一种面板,它将控件布局在顶部、底部、左侧和右侧,由单个控件填充中间的剩余空间。

xmlns:views=“clr-namespace:Todo.Views”
views:TodoListView/
我们希望显示刚刚在Todo中创建的控件。需要加一下命名空间。任何不是核心Avalonia控件的控件通常都需要这种类型的映射,以便XAML引擎找到该控件。
5.创建模型,它将表示我们的数据,因为它将存储在数据库中。我们的模型将非常简单:每个TODO项将由一个文本描述和一个表示是否选中该项的布尔值组成。将以下类放在项目的Models目录中:Models/Todoltem.cs
6.创建视图模型。这个类将为我们的视图提供数据。我们已经创建了视图并将其命名为TodoListView因此相关的视图模型将称为TodoListViewlodel。把这个类放在你项目的Viewllodels目录中:ViewModels / TodoListViewModel cs
7.连接视图,现在我们已经设置了视图模型,我们需要让视图使用这些视图模型。我们通过使用Avalonia的数据绑定特性来做到这一点
主要的变化是,我们不再使用views:TodoListView/控件作为窗口的内容,而是将窗口的内容绑定到MainWindowViewModel。Content=“{Binding List}”

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Class="Todo.Views.MainWindow"
        Icon="/Assets/avalonia-logo.ico"
        Width="200" Height="300"
        Title="Avalonia Todo"
        Content="{Binding List}">
</Window>

8.从视图中获取数据
首先要注意的是,我们已经将控件更改为ItemsControl:

<ItemsControl Items="{Binding Items}">
<UserControl xmlns="https://github.com/avaloniaui"
             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"
             mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="300"
             x:Class="Todo.Views.TodoListView">
  <DockPanel>
    <Button DockPanel.Dock="Bottom"
            HorizontalAlignment="Center">
      Add an item
    </Button>
    <ItemsControl Items="{Binding Items}">
      <ItemsControl.ItemTemplate>
        <DataTemplate>
          <CheckBox Margin="4"
                    IsChecked="{Binding IsChecked}"
                    Content="{Binding Description}"/>
        </DataTemplate>
      </ItemsControl.ItemTemplate>
    </ItemsControl>
  </DockPanel>
</UserControl>

ItemsControl是一个非常简单的控件,用于显示分配给Items属性的集合中的每一项。这里每个项目都是TodoItem模型的一个实例,就像TodoListViewModel一样。每个项的显示方式由ItemTemplate控制。ItemTemplate接受一个DataTemplate,其内容对于每个项都是重复的。在本例中,我们将每个项目显示为CheckBox,检查状态绑定到TodoItenViewModel的IsChecked属性,内容绑定到Description

<ItemsControl.ItemTemplate>
  <DataTemplate>
    <CheckBox Margin="4"
              IsChecked="{Binding IsChecked}"
              Content="{Binding Description}"/>
  </DataTemplate>
</ItemsControl.ItemTemplate>

9.ViewLocator定义了一个数据模板,用于将视图模型转换为视图。它定义了两个方法:Match(对象数据)查看数据,如果数据继承自ViewModelBase,则返回true,表示应该调用BuildBuild(对象数据)采用数据类型的完全限定名,并将字符串“ViewModel”替换为字符串“View”。然后,它尝试获取与该名称匹配的类型。如果找到匹配的类型,则创建该类型的实例并返回该实例
应用程序中有一个ViewLocator的实例。Application.DataTemplates

<Application xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:Todo"
             x:Class="Todo.App">
    <Application.DataTemplates>
        <local:ViewLocator/>
    </Application.DataTemplates>
​
    <Application.Styles>
        <StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
        <StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
    </Application.Styles>
</Application>

当ContentControl的实例(如Window)将其Content属性设置为非控件时,它会在控件树中搜索与内容数据匹配的DataTemplate。如果没有其他DataTemplate与数据匹配,它将最终到达应用程序数据模板中的ViewLocator, ViewLocator将执行其业务并返回相应视图的实例。

10.调用方法
Command属性描述单击按钮时要调用的命令,我们将它绑定到$parent[Window].DataContext.AddItem
$parent [Window]表示查找Window类型的祖先控件并获取它的DataContext
(即在本例中是MainWindowViewModel)并绑定到该视图模型上的AddItem方法

<Button DockPanel.Dock="Bottom"
            Command="{Binding $parent[Window].DataContext.AddItem}">
      Add an item
    </Button>

11.添加数据以及取消的命令

public AddItemViewModel()
        {
            var okEnabled = this.WhenAnyValue(
                x => x.Description,
                x => !string.IsNullOrWhiteSpace(x));
​
            Ok = ReactiveCommand.Create(
                () => new TodoItem { Description = Description }, 
                okEnabled);
            Cancel = ReactiveCommand.Create(() => { });
        }
        public ReactiveCommand<Unit, TodoItem> Ok { get; }
        public ReactiveCommand<Unit, Unit> Cancel { get; }

首先,我们修改Description属性以引发更改通知。我们以前在主窗口视图模型中看到过这种模式。在这种情况下,我们为ReactiveUI实现变更通知,而不是专门为Avalonia实现:

    x => x.Description,
    x => !string.IsNullOrWhiteSpace(x));

现在Description已经启用了更改通知,我们可以使用WhenAnyValue将属性转换为IObservable形式的值流。以上代码可以理解为:对于Description的初始值,以及后续的更改选择使用该值调用string.IsNullOrWhiteSpace()结果的倒数这意味着okEnabled表示bool值流,当Description为非空字符串时将生成true,当Description为空字符串时将生成false。这正是我们想要的OK按钮启用。
然后我们创建一个ReactiveCommand并将其分配给Ok属性:

    () => new TodoItem { Description = Description }, 
    okEnabled);

ReactiveCommand的第二个参数。Create控制命令的启用状态,因此我们刚刚创建的可观察对象被传递到那里。第一个参数是在执行命令时运行的lambda。在这里,我们简单地用用户输入的描述创建模型TodoItem的实例。我们还为“Cancel”按钮创建了一个命令:

取消命令总是启用的,所以我们不传递一个可观察对象来控制它的状态,我们只是传递一个“execute”lambda,在这种情况下,它什么都不做。

12.绑定OK和Cancel按钮现在我们可以将视图中的OK和Cancel按钮绑定到我们刚才只在视图模型中创建的OK和Cancel命令:

             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"
             mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="300"
             x:Class="Todo.Views.AddItemView">
  <DockPanel>
    <Button DockPanel.Dock="Bottom" Command="{Binding Cancel}">Cancel</Button>
    <Button DockPanel.Dock="Bottom" Command="{Binding Ok}">OK</Button>
    <TextBox AcceptsReturn="False"
             Text="{Binding Description}"
             Watermark="Enter your TODO"/>
  </DockPanel>
</UserControl>

14.这段代码利用了ReactiveCommand本身是一个可观察对象的事实,它在每次执行命令时都会产生一个值。你会注意到,当我们定义这些命令时,它们的声明略有不同

public ReactiveCommand<Unit, TodoItem> Ok { get; }
public ReactiveCommand<Unit, Unit> Cancel { get; }

reactivecommand第二个类型参数指定执行命令时产生的结果类型。Ok生成TodoItem,而Cancel生成Unit。单位是虚空的反应版本。这意味着该命令不会产生任何值。可观察到的。Merge将任意数量的可观测数据的输出组合在一起,并将它们合并为单个可观察数据流。因为它们被合并到一个流中,所以它们需要具有相同的类型。因此,我们调用vm.Cancel.Select (_ => (TodoItem)null):这样做的效果是,每当Cancel可观察对象产生一个值时,我们都会选择一个空TodoItem。
.Take(1)
我们只对第一次点击OK或Cancel按钮感兴趣;一旦点击了其中一个按钮,我们就不需要再听任何点击了。Take(1)表示“只取可观察序列产生的第一个值”。
15.最后,我们订阅可观察序列的结果。如果该命令导致生成了一个模型(即单击OK),那么将该模型添加到列表中。然后我们将Content设置回List,以便在窗口中显示列表并隐藏“AddltemView”。

{
    if (model != null)
    {
        List.Items.Add(model);
    }
​
    Content = List;
});

1.基本的使用方法

axaml中定义 后台找到并设置对象
在WPF中,当你在xaml文件中定义完UI并设置x:Name就可以在后台中直接使用对象名称进行操作.那是因为vs在你设计时自动生成了.g.i.cs文件(你可以在/obj中看到)

而ava中不同,你需要在后台中自己Get到这个UI对象(与Android类似): 例如:

axaml中定义一个名称为 TB_Title的TextBlock文本标签:

<TextBlock x:Name="TB_Title" HorizontalAlignment="Center" Foreground="White" FontSize="14" VerticalAlignment="Center" Margin="10,0,0,10" Text="My Avalonia Desktop App"/>

在cs中定义并更改标签内容:

TextBlock TB_Title = this.Get<TextBlock>("TB_Title");
TB_Title.Text = "嘻嘻";

这里就用到了this.Get(string Name)方法

T:表示对象类型 Name:为x:Name中定义的名称

注意:在后台查找UI对象 若不是局部变量 应需考虑 时序问题 否则在使用时对象可能是null

建议将所有的控件优先查找出来(如果你控制得比较好可以不用…)

事件
详细的介绍可以看官方文档:http://avaloniaui.net/docs/input/events

有几点比较坑的地方:

1.直接在axaml中定义事件有时候不会成功,可以在后台中定义,例如:

<Button x:Name="btn" Click="Btn_Click">Click Me</Button>
void Btn_Click(object sender, RoutedEventArgs args)
{
   //...
}

若不成功可以:

this.Get<Button>("btn").Click+=Btn_Click;

2.样式设置

标签中增加一下内容,可以为界面元素设置样式

<Window.Styles>
    <Style Selector="TextBox.tb1">
      <Setter Property="Margin" Value="0,-40,0,0"/>
      <Setter Property="Height" Value="26"/>
      <Setter Property="Width" Value="250"/>
      <Setter Property="Watermark" Value="账号"/>
      <Setter Property="BorderBrush" Value="#80c0ff"/>
    </Style>
</Window.Styles>

2.1.窗口标题栏消除掉

    ExtendClientAreaToDecorationsHint="True"
    ExtendClientAreaChromeHints="NoChrome"
    ExtendClientAreaTitleBarHeightHint="-1"

2.2visualbrush画分割线

DestinationRect=“10,10,10,10” SourceRect=“0,0,0,0”
我感觉是矩形坐标,我画的分割线宽度是10

        <Border.Background>
            <VisualBrush
                AlignmentX="Left"
                AlignmentY="Top"
                DestinationRect="10,10,10,10"
                SourceRect="0,0,0,0"
                Stretch="None"
                TileMode="Tile">
                <VisualBrush.Visual>
                    <Grid Width="10" Height="10">
                        <Line
                            Stroke="DarkGray"
                            StrokeThickness="2"
                            StartPoint="0,10"
                            EndPoint="10,0" />
                        <Line
                            Stroke="DarkGray"
                            StrokeThickness="2"
                            StartPoint="10,10"
                            EndPoint="0,0" />
                    </Grid>
                </VisualBrush.Visual>
            </VisualBrush>
        </Border.Background>

2.3文本框

<TextBox Text="{Binding Greeting, Mode=OneWay}"
						 IsReadOnly="True"//只读
						 FontWeight="Bold"//加粗
						 Watermark="请填写..."//没有输入时显示的内容
						 TextWrapping=“Wrap”//换行:不换行使用省略号表示:TextWrapping=“NoWrap”                      
						  TextTrimming=“CharacterEllipsis”
						  默认TextWrapping=“NoWrap”
                           PointerPressed=“”// 点击事件
						  />

2.4容器

设置填充时,只有在dockPanel中才有效果

 <StackPanel Margin="10" Orientation="Horizontal"
            Margin="32,0,32,4"//左,上,右,下
            >//布局
  </StackPanel>

2.5边框

<Border BorderBrush="Red" BorderThickness="1">//设置边框宽度和颜色,必须要一起有,不然没效果
      
    </Border>

2.6滚动条

<ScrollViewer 
HorizontalScrollBarVisibility="Auto"//水平滚动条可见度:不设置时只显示上下的,设置成Auto时会左右,上下滚动

>

</ScrollViewer>

2.7Grid设置行列

Panel panel1 = new Panel();

Grid.SetRow(panel1 , 3);
Grid.SetColumn(panel1, 1);
Grid.SetColumnSpan(panel1, 2);
//或者
panel1.SetValue(Grid.RowProperty, 3);
panel1.SetValue(Grid.ColumnProperty, 1);
panel1.SetValue(Grid.ColumnSpanProperty, 2);

//设置所属父控件
grid1.Children.Add(panel1 );

2.8Calendar 日历

用法:

<Calendar 
IsTodayHighlighted="true"//当前选中日期是否高亮,默认高亮
 SelectionMode="SingleDate">
        </Calendar>

2.9image控件

添加到项目中的图片设置为avalonia的资源文件后

<Image Margin="0"
											   VerticalAlignment="Center"
											   HorizontalAlignment="Center"
											   Width="16" Height="16"
											   Source="avares://CreateCtrlByType/Images/down.png">

Image.Source=图片路径

Image image = new Image
{
  Source = new DrawingImage
  {
   Drawing = new ImageDrawing
   {
     ImageSource = new Bitmap(BitmapPath),
     Rect = new Rect(0, 0, 200, 200),
   }
 }
}

Name=“{Binding DataItemId, StringFormat=‘itemsContainer{0}’}”

2.10下拉框

SelectionChanged:当控件的选择发生更改时发生。继承自SelectingltemsControl

ComboBox cmb = new ComboBox()
                    {
                        [!ComboBox.SelectedItemProperty] = new Binding("DataItemValue", BindingMode.TwoWay),//双向绑定数据
                        [!ToolTip.TipProperty] = new Binding("DataItemValue")//悬浮显示内容
                    };

2.11ListBox

样式

          <Style Selector="ListBox">
            <Setter Property="Background" Value="White"/>
            <Setter Property="Foreground" Value="Black" />
            <Setter Property="Margin" Value="0" />
            <Setter Property="Padding" Value="0" />
          </Style>
          <Style Selector="ListBoxItem">
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="Margin" Value="0"/>
            <Setter Property="Padding" Value="0"/>
            <Setter Property="Height" Value="22"/>
          </Style>
          <Style Selector="ListBoxItem:pointerover /template/ ContentPresenter">
            <!--鼠标移入-->
            <Setter Property="Background" Value="#007CCC"/>
          </Style>
          <Style Selector="ListBoxItem:pointerover > DockPanel > TextBlock">
            <!--鼠标移入-->
            <Setter Property="Foreground" Value="White" />
          </Style>
          <Style Selector="ListBoxItem:selected /template/ ContentPresenter">
            <!--选中时的背景-->
            <Setter Property="Background" Value="#007CCC"/>
          </Style>
          <Style Selector="ListBoxItem:selected > DockPanel > TextBlock">
            <Setter Property="Foreground" Value="White" />
          </Style>
          <Style Selector="ListBoxItem:selected:pointerover /template/ ContentPresenter">
            <!--选中时,鼠标移入-->
            <Setter Property="Background" Value="#007CCC"/>
          </Style>
        </ListBox.Styles>

2.12button

              <Style Selector="Button:pointerover ContentPresenter">//鼠标悬浮的背景
                <Setter Property="Background" Value="Red"/>
              </Style>
            <Style Selector="Button:pointerover AccessText">//鼠标悬浮字体颜色
                <Setter Property="Foreground" Value="Yellow"/>
              </Style>
            </Button.Styles>
            

3.注意事项

3.1双向绑定list集合

如以下代码,绑定了 ListUserInfoList
在删除某一项时,界面可能不会变化,且可能出现异常

                       SelectedIndex="0"
                      Cursor="Hand"
               Name="listBox"
                          >
        <ListBox.ItemTemplate>
          <DataTemplate>
            <DockPanel>
              <TextBlock Margin="8,0,0,0" Text="{Binding UserName}"/>
              <TextBlock Margin="0,0,8,0" HorizontalAlignment="Right" Tag="{Binding UserName}" Foreground="White" Text="X" PointerPressed="delUserName_PointerPressed" />
            </DockPanel>
          </DataTemplate>
        </ListBox.ItemTemplate>
      </ListBox>

解决方案,使用ObservableCollection代替List

3.2属性Bounds

例如元素的真实高宽:Bounds.Height/Width
元素相对于父控件的位置:Bounds.Position.X/Y

MaxWidth=“800” MaxHeight=“450” MinWidth=“800” MinHeight=“450”

3.3 tab标题样式

3.4TextBox没有TextChanged事件,所以使用属性改变事件监视Text属性

txtBox.PropertyChanged += TxtBox_PropertyChanged;
private void TxtBox_PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
var property = e.Property.Name;
if (property == “Text”)
{
//处理
}
}
 类似资料: