这是典型的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条目有关。好了,这就是样板文件!
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;
});
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;
标签中增加一下内容,可以为界面元素设置样式
<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>
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
ExtendClientAreaTitleBarHeightHint="-1"
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>
<TextBox Text="{Binding Greeting, Mode=OneWay}"
IsReadOnly="True"//只读
FontWeight="Bold"//加粗
Watermark="请填写..."//没有输入时显示的内容
TextWrapping=“Wrap”//换行:不换行使用省略号表示:TextWrapping=“NoWrap”
TextTrimming=“CharacterEllipsis”
默认TextWrapping=“NoWrap”
PointerPressed=“”// 点击事件
/>
Avalonia.Controls.TextBox没有TextChanged事件,所以使用属性改变事件监视Text属性
txtBox.PropertyChanged += TxtBox_PropertyChanged;
private void TxtBox_PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
var property = e.Property.Name;
if (property == “Text”)
{
//处理
}
}
设置填充时,只有在dockPanel中才有效果
<StackPanel Margin="10" Orientation="Horizontal" Margin="32,0,32,4"//左,上,右,下 >//布局 </StackPanel>
<Border BorderBrush="Red" BorderThickness="1">//设置边框宽度和颜色,必须要一起有,不然没效果
</Border>
<ScrollViewer
HorizontalScrollBarVisibility="Auto"//水平滚动条可见度:不设置时只显示上下的,设置成Auto时会左右,上下滚动
>
</ScrollViewer>
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 );
用法:
IsTodayHighlighted="true"//当前选中日期是否高亮,默认高亮
SelectionMode="SingleDate">
</Calendar>
添加到项目中的图片设置为avalonia的资源文件后
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}’}”
SelectionChanged:当控件的选择发生更改时发生。继承自SelectingltemsControl
{
[!ComboBox.SelectedItemProperty] = new Binding("DataItemValue", BindingMode.TwoWay),//双向绑定数据
[!ToolTip.TipProperty] = new Binding("DataItemValue")//悬浮显示内容
};
样式
<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>
<Style Selector="Button:pointerover ContentPresenter">//鼠标悬浮的背景
<Setter Property="Background" Value="Red"/>
</Style>
<Style Selector="Button:pointerover AccessText">//鼠标悬浮字体颜色
<Setter Property="Foreground" Value="Yellow"/>
</Style>
</Button.Styles>
如以下代码,绑定了 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
例如元素的真实高宽:Bounds.Height/Width
元素相对于父控件的位置:Bounds.Position.X/Y
MaxWidth=“800” MaxHeight=“450” MinWidth=“800” MinHeight=“450”