借助网络配置,控制Modbus工具的日志显示,避免卡死界面
智能大石头 authored at 2021-12-11 22:09:19
6.90 KiB
XCoder
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Threading;
using System.Collections.Specialized;
using XCoderAv.ViewModels;

namespace XCoderMac.Views;

/// <summary>
/// Mac 原生数据同步界面。复用 XCoderAv.DataSyncViewModel 的连接/表选择/同步业务,
/// 修复原窗口把布尔值 IsSourceConnected 显示为 True 的错误绑定。
/// </summary>
public partial class MacDataSyncWindow : Window
{
    public DataSyncViewModel ViewModel { get; }

    public MacDataSyncWindow()
    {
        InitializeComponent();
        ViewModel = new DataSyncViewModel();
        DataContext = ViewModel;

        // 源连接在打开时已加载;目标连接通常要等源数据库连接成功后才会出现。
        // 两组列表都支持首次选中第一项,同时保留用户已选的有效连接。
        ViewModel.Connections.CollectionChanged += ConnectionListChanged;
        ViewModel.TargetConnections.CollectionChanged += ConnectionListChanged;
        SelectFirstAvailableConnection();

        // 两组按钮状态统一以“实际连接成功”的 VM 状态为准。
        // 原本目标库按钮仅绑定 IsSourceConnected,目标已连上后仍可重复点击。
        ViewModel.PropertyChanged += (_, e) =>
        {
            if (e.PropertyName is nameof(DataSyncViewModel.IsSourceConnected)
                or nameof(DataSyncViewModel.IsTargetConnected))
                UpdateConnectionActions();
        };
        UpdateConnectionActions();
    }

    private bool _selectionUpdateQueued;

    private void ConnectionListChanged(object? sender, NotifyCollectionChangedEventArgs e)
    {
        // 目标连接通过 Clear/Add 逐个填入;等本轮操作结束再选首项,
        // 避免列表刷新过程中多次改变选中项。
        if (_selectionUpdateQueued) return;
        _selectionUpdateQueued = true;
        Dispatcher.UIThread.Post(() =>
        {
            _selectionUpdateQueued = false;
            SelectFirstAvailableConnection();
        }, DispatcherPriority.Background);
    }

    private void SelectFirstAvailableConnection()
    {
        var source = ViewModel.Connections;
        if (!source.Contains(ViewModel.SourceConn))
            ViewModel.SourceConn = source.FirstOrDefault() ?? "";

        var target = ViewModel.TargetConnections;
        if (!target.Contains(ViewModel.TargetConn))
            ViewModel.TargetConn = target.FirstOrDefault() ?? "";
    }

#if DEBUG
    /// <summary>只使用本地模拟列表验证首项选中及列表刷新,不会连接数据库。</summary>
    public async Task SmokeTestConnectionDefaultsAsync()
    {
        var initialSources = ViewModel.Connections.ToArray();
        var initialTargets = ViewModel.TargetConnections.ToArray();
        var initialSource = ViewModel.SourceConn;
        var initialTarget = ViewModel.TargetConn;

        try
        {
            ViewModel.Connections.Clear();
            ViewModel.TargetConnections.Clear();
            await Task.Delay(65);
            if (!string.IsNullOrEmpty(ViewModel.SourceConn) ||
                !string.IsNullOrEmpty(ViewModel.TargetConn))
                throw new InvalidOperationException("空列表下拉框不应显示旧选中项");

            ViewModel.Connections.Add("Source_A");
            ViewModel.Connections.Add("Source_B");
            await Task.Delay(65);
            if (ViewModel.SourceConn != "Source_A" ||
                SourceDatabaseCombo.SelectedItem?.ToString() != "Source_A")
                throw new InvalidOperationException("源数据库没有默认显示第一项");

            ViewModel.SourceConn = "Source_B";
            ViewModel.Connections.Add("Source_C");
            await Task.Delay(65);
            if (ViewModel.SourceConn != "Source_B")
                throw new InvalidOperationException("源数据库更新列表覆盖了用户选中的项目");

            ViewModel.TargetConnections.Add("Target_A");
            ViewModel.TargetConnections.Add("Target_B");
            await Task.Delay(65);
            if (ViewModel.TargetConn != "Target_A" ||
                TargetDatabaseCombo.SelectedItem?.ToString() != "Target_A")
                throw new InvalidOperationException("目标数据库加载后没有显示第一项");

            ViewModel.TargetConn = "Target_B";
            ViewModel.TargetConnections.Add("Target_C");
            await Task.Delay(65);
            if (ViewModel.TargetConn != "Target_B")
                throw new InvalidOperationException("目标列表更新覆盖了用户已选择的连接");

            ViewModel.TargetConnections.Clear();
            await Task.Delay(65);
            if (!string.IsNullOrEmpty(ViewModel.TargetConn))
                throw new InvalidOperationException("目标库列表清空后仍然显示旧连接");

            Console.WriteLine("SYNC DEFAULTS OK: source first, target first after load, preserve selection, clear on empty");
        }
        finally
        {
            ViewModel.Connections.Clear();
            foreach (var item in initialSources) ViewModel.Connections.Add(item);
            ViewModel.TargetConnections.Clear();
            foreach (var item in initialTargets) ViewModel.TargetConnections.Add(item);
            await Task.Delay(65);
            ViewModel.SourceConn = initialSources.Contains(initialSource)
                ? initialSource : initialSources.FirstOrDefault() ?? "";
            ViewModel.TargetConn = initialTargets.Contains(initialTarget)
                ? initialTarget : initialTargets.FirstOrDefault() ?? "";
        }
    }
#endif

    private void UpdateConnectionActions()
    {
        var sourceConnected = ViewModel.IsSourceConnected;
        var targetConnected = ViewModel.IsTargetConnected;

        ConnectSourceButton.IsEnabled = !sourceConnected;
        DisconnectSourceButton.IsEnabled = sourceConnected;

        // 目标库必须先连源库;连接成功后也必须禁止重复点击。
        ConnectTargetButton.IsEnabled = sourceConnected && !targetConnected;
        DisconnectTargetButton.IsEnabled = targetConnected;
    }

    // 数据同步窗口以内容形式嵌入主窗体,实际 SizeChanged 来源必须是可见视图。
    private void PageViewport_SizeChanged(object? sender, SizeChangedEventArgs e)
    {
        var available = e.NewSize.Width - 50;
        if (available <= 0) return;
        var wide = available >= 800;
        if (_groupsSideBySide == wide) return;
        _groupsSideBySide = wide;

        ConnectionGroups.ColumnDefinitions = wide
            ? new ColumnDefinitions("*,16,*")
            : new ColumnDefinitions("*");
        ConnectionGroups.RowDefinitions = wide
            ? new RowDefinitions("Auto")
            : new RowDefinitions("Auto,16,Auto");
        Grid.SetColumn(SourcePanel, 0);
        Grid.SetRow(SourcePanel, 0);
        Grid.SetColumn(TargetPanel, wide ? 2 : 0);
        Grid.SetRow(TargetPanel, wide ? 0 : 2);
    }

    private bool? _groupsSideBySide;
}