Files
AtomUI/docs/releases/6.0.8-api-changes.md

8.3 KiB

AtomUI 6.0.8 API Change Examples

This document supplements the main 6.0.8 release notes with the most important API changes and migration examples from 6.0.7. For the authoritative release entries, see the repository root CHANGELOG.md and CHANGELOG.zh-CN.md.

Chinese version: 6.0.8 API 变更示例.

ImagePreviewer Image Source Migration

6.0.8 unifies ImagePreviewer image sources around IImagePreviewSource:

  • SourceUri -> Source
  • SourceUris -> Sources
  • FallbackSourceUri -> FallbackSource
  • Use UriImagePreviewSource for URI, local file, and Avalonia resource images.
  • Use StreamImagePreviewSource for database, authenticated network, encrypted file, memory, and other lazy stream sources.

6.0.7

<atom:ImagePreviewer
    SourceUri="https://example.com/photo.jpg"
    FallbackSourceUri="avares://Demo/Assets/fallback.png" />

6.0.8

using AtomUI.Desktop.Controls;

public IImagePreviewSource PreviewSource { get; } =
    new UriImagePreviewSource("https://example.com/photo.jpg");

public IImagePreviewSource PreviewFallbackSource { get; } =
    new UriImagePreviewSource("avares://Demo/Assets/fallback.png");
<atom:ImagePreviewer
    Source="{Binding PreviewSource}"
    FallbackSource="{Binding PreviewFallbackSource}" />

Multiple Images, Cover, and Preloading

CoverIndex only controls which image is shown as the closed-state cover. It does not change CurrentIndex.

public IList<IImagePreviewSource> PreviewImages { get; } =
[
    new UriImagePreviewSource("https://example.com/photo-1.jpg"),
    new UriImagePreviewSource("https://example.com/photo-2.jpg"),
    new UriImagePreviewSource("https://example.com/photo-3.jpg")
];

public int CurrentPreviewIndex { get; set; }
<atom:ImageGroupPreviewer
    Sources="{Binding PreviewImages}"
    CoverIndex="1"
    CurrentIndex="{Binding CurrentPreviewIndex}"
    MaxConcurrentLoads="4"
    PreloadCount="1" />

Lazy Stream Sources

OpenReadAsync should return a new readable stream each time it is called. ImagePreviewer owns and disposes the returned stream.

public IImagePreviewSource SecurePreviewSource { get; } =
    new StreamImagePreviewSource(
        openReadAsync: cancellationToken =>
        {
            Stream stream = File.OpenRead("/secure/photos/invoice.png");
            return new ValueTask<Stream>(stream);
        },
        displayName: "invoice.png",
        contentType: "image/png",
        identity: "/secure/photos/invoice.png");

Upload Composable Trigger Migration

In 6.0.8, the root Upload control owns file state, the queue, Form values, and lifecycle. File selection, directory selection, and drop input are handled by composable controls.

6.0.7

<atom:Upload
    IsUploadDirectoryEnabled="True"
    IsShowUploadTrigger="True"
    DefaultTaskList="{Binding DefaultTaskList}"
    UploadTransport="{Binding UploadTransport}" />

6.0.8

using AtomUI.Desktop.Controls;
using System.Collections.ObjectModel;

public ObservableCollection<UploadFileItem> Attachments { get; } = [];

public IFileUploadTransport UploadTransport { get; } = new MyUploadTransport();
<atom:Upload
    Files="{Binding Attachments}"
    UploadTransport="{Binding UploadTransport}"
    AutoUpload="True"
    ListMaxHeight="180"
    ListScrollBarVisibility="Auto"
    SuccessAutoRemoveDelay="0:0:3">
    <atom:Space>
        <atom:UploadTrigger SourceKind="Files">
            <atom:Button Content="Select files" />
        </atom:UploadTrigger>
        <atom:UploadTrigger SourceKind="Directories">
            <atom:Button Content="Select directory" />
        </atom:UploadTrigger>
    </atom:Space>
</atom:Upload>

Drag-and-Drop Upload

<atom:Upload
    Files="{Binding Attachments}"
    UploadTransport="{Binding UploadTransport}">
    <atom:UploadDropZone>
        <TextBlock Text="Drop files here" />
    </atom:UploadDropZone>
</atom:Upload>

Form Value Projection

FileValueMode controls the value Upload writes into Form:

<atom:Upload
    Files="{Binding Attachments}"
    FileValueMode="Results"
    UploadTransport="{Binding UploadTransport}" />

New OtpLineEdit Control

OtpLineEdit uses one Text value for the complete verification code. Cells, separators, and masking only affect visuals.

<atom:OtpLineEdit
    Text="{Binding Code}"
    Length="6"
    InputMode="Numeric"
    Separator="-"
    SeparatorInterval="3"
    StyleVariant="Outlined" />
<atom:OtpLineEdit
    Text="{Binding RecoveryCode}"
    Length="8"
    InputMode="AlphaNumeric"
    IsMasked="True"
    PlaceholderText="•" />
public string? Code { get; set; }

Form and DataValidationErrors

In 6.0.8, errors produced by Form validators are written to Avalonia DataValidationErrors. If a ViewModel validator, binding validator, or application validation layer already provides a native validation error, AtomUI controls display that native error with highest priority.

The default trigger is OnChanged. Configure OnBlur explicitly when validation should run after focus leaves the field:

<atom:Form ValidateTrigger="OnBlur">
    <atom:FormItem Name="UserName" IsRequired="True">
        <atom:LineEdit Text="{Binding UserName}" />
    </atom:FormItem>
</atom:Form>

Default Two-Way Bindings

Several selection and value properties now use BindingMode.TwoWay by default. Common scenarios no longer need explicit Mode=TwoWay:

<atom:Select
    OptionsSource="{Binding CityOptions}"
    SelectedOption="{Binding SelectedCity}" />

<atom:TreeSelect
    ItemsSource="{Binding TreeNodes}"
    SelectedItems="{Binding SelectedTreeNodes}" />

<atom:DatePicker SelectedDateTime="{Binding SelectedDate}" />

<atom:TimePicker SelectedTime="{Binding SelectedTime}" />

<atom:Rate Value="{Binding Rating}" />

<atom:Dialog IsOpen="{Binding IsSettingsOpen}" />

If application logic needs a read-only projection, set Mode=OneWay explicitly on the binding.

DataGrid Filter Model

Column filters now live directly on column APIs, and both the filter item source and selected values are bindable:

<atom:DataGridTextColumn
    Header="Name"
    Binding="{Binding Name}"
    Filters="{Binding NameFilters}"
    SelectedFilterValues="{Binding SelectedNames}"
    FilterTextMemberPath="Text"
    FilterValueMemberPath="Value"
    FilterPresenterMode="Menu"
    FilterSelectionMode="Multiple"
    FilterApplyMode="SelectionChanged" />

Tree filters use FilterChildrenMemberPath:

<atom:DataGridTextColumn
    Header="Department"
    Binding="{Binding Department}"
    Filters="{Binding DepartmentFilters}"
    SelectedFilterValues="{Binding SelectedDepartments}"
    FilterTextMemberPath="Name"
    FilterValueMemberPath="Id"
    FilterChildrenMemberPath="Children"
    FilterPresenterMode="Tree" />

Select, ComboBox, and Cascader Overflow Tips

Enable overflow tooltips when selected content cannot be fully displayed:

<atom:Select
    IsShowOverflowTip="True"
    OverflowTipDelay="800"
    OverflowTipPlacement="TopEdgeAlignedLeft"
    OptionsSource="{Binding Options}"
    SelectedOptions="{Binding SelectedOptions}" />

<atom:ComboBox
    IsShowOverflowTip="True"
    OverflowTipDelay="800"
    OverflowTipPlacement="TopEdgeAlignedLeft"
    ItemsSource="{Binding Items}" />

TabControl and TabStrip Reordering

<atom:TabControl
    IsTabReorderEnabled="True"
    TabActivationTrigger="PointerReleased"
    TabReordering="HandleTabReordering"
    TabReordered="HandleTabReordered">
    <atom:TabItem Header="Tab 1">Content 1</atom:TabItem>
    <atom:TabItem Header="Tab 2">Content 2</atom:TabItem>
    <atom:TabItem Header="Tab 3">Content 3</atom:TabItem>
</atom:TabControl>

TabActivationTrigger="PointerReleased" is the default and fits tab reordering scenarios. Use PointerPressed when tabs should activate as soon as the pointer is pressed.

Dialog Async Close Validation

await Dialog.ShowDialogAsync(
    new TextBlock { Text = "Close without saving?" },
    options: new DialogOptions
    {
        Title = "Unsaved changes",
        BeforeCloseAsync = async context =>
        {
            if (context.Reason == DialogCloseReason.Rejected)
            {
                return true;
            }

            return await ConfirmSaveAsync();
        }
    });