Files
v2rayN/v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs
T
DHR60 8b93d35766 Add ProDataGrid (#10347)
* Bump Avalonia.Desktop from 12.1.2 to 12.1.4

---
updated-dependencies:
- dependency-name: Avalonia.Desktop
  dependency-version: 12.1.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Remove Avalonia.Controls.DataGrid

* Add ProDataGrid Feature

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2026-11-01 20:34:24 +08:00

620 lines
22 KiB
C#

using Avalonia.Controls.DataGridDragDrop;
using DialogHostAvalonia;
using v2rayN.Desktop.Common;
namespace v2rayN.Desktop.Views;
public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
{
private static Config _config;
private static readonly string _tag = "ProfilesView";
private bool _isDndRegistered = false;
public ProfilesView()
{
InitializeComponent();
_config = AppManager.Instance.Config;
menuSelectAll.Click += menuSelectAll_Click;
btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click;
txtServerFilter.KeyDown += TxtServerFilter_KeyDown;
lstProfiles.KeyDown += LstProfiles_KeyDown;
lstProfiles.SelectionChanged += lstProfiles_SelectionChanged;
lstProfiles.DoubleTapped += LstProfiles_DoubleTapped;
lstProfiles.Sorting += LstProfiles_Sorting;
//if (_config.UiItem.EnableDragDropSort)
//{
// lstProfiles.SetValue(DragDrop.AllowDropProperty, true);
// lstProfiles.AddHandler(PointerPressedEvent, LstProfiles_PointerPressed, RoutingStrategies.Bubble, true);
// lstProfiles.AddHandler(PointerMovedEvent, LstProfiles_PointerMoved, RoutingStrategies.Bubble, true);
// lstProfiles.AddHandler(PointerReleasedEvent, LstProfiles_PointerReleased, RoutingStrategies.Bubble, true);
// lstProfiles.AddHandler(DragDrop.DragOverEvent, LstProfiles_DragOver, RoutingStrategies.Bubble);
// lstProfiles.AddHandler(DragDrop.DropEvent, LstProfiles_Drop, RoutingStrategies.Bubble);
//}
this.WhenActivated(disposables =>
{
this.OneWayBind(ViewModel, vm => vm.ProfileItems, v => v.lstProfiles.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedProfile, v => v.lstProfiles.SelectedItem).DisposeWith(disposables);
// this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.lstGroup.ItemsSource).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSub, v => v.lstGroup.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ServerFilter, v => v.txtServerFilter.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddSubCmd, v => v.btnAddSub).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.EditSubCmd, v => v.btnEditSub).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.EditSubCmd, v => v.menuSubEdit).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddSubCmd, v => v.menuSubAdd).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.DeleteSubCmd, v => v.menuSubDelete).DisposeWith(disposables);
//servers delete
this.BindCommand(ViewModel, vm => vm.EditServerCmd, v => v.menuEditServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RemoveServerCmd, v => v.menuRemoveServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RemoveDuplicateServerCmd, v => v.menuRemoveDuplicateServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.CopyServerCmd, v => v.menuCopyServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SetDefaultServerCmd, v => v.menuSetDefaultServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ShareServerCmd, v => v.menuShareServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupAllServerCmd, v => v.menuGenGroupAllServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupRegionServerCmd, v => v.menuGenGroupRegionServer).DisposeWith(disposables);
//servers move
//this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);
//this.Bind(ViewModel, vm => vm.SelectedMoveToGroup, v => v.cmbMoveToGroup.SelectedItem).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.MoveTopCmd, v => v.menuMoveTop).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.MoveUpCmd, v => v.menuMoveUp).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.MoveDownCmd, v => v.menuMoveDown).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.MoveBottomCmd, v => v.menuMoveBottom).DisposeWith(disposables);
//servers ping
this.BindCommand(ViewModel, vm => vm.MixedTestServerCmd, v => v.menuMixedTestServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.TcpingServerCmd, v => v.menuTcpingServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RealPingServerCmd, v => v.menuRealPingServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.UdpTestServerCmd, v => v.menuUdpTestServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SpeedServerCmd, v => v.menuSpeedServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SortServerResultCmd, v => v.menuSortServerResult).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.RemoveInvalidServerResultCmd, v => v.menuRemoveInvalidServerResult).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.FastRealPingCmd, v => v.btnFastRealPing).DisposeWith(disposables);
//servers export
this.BindCommand(ViewModel, vm => vm.Export2ClientConfigCmd, v => v.menuExport2ClientConfig).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.Export2ClientConfigClipboardCmd, v => v.menuExport2ClientConfigClipboard).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.Export2ShareUrlCmd, v => v.menuExport2ShareUrl).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.Export2ShareUrlBase64Cmd, v => v.menuExport2ShareUrlBase64).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.Export2InnerUriCmd, v => v.menuExport2InnerUri).DisposeWith(disposables);
if (_config.UiItem.EnableDragDropSort && !_isDndRegistered)
{
lstProfiles.CanUserReorderRows = true;
lstProfiles.RowDragHandle = DataGridRowDragHandle.RowHeaderAndRow;
lstProfiles.RowDragHandleVisible = true;
var rowDragDropOptions = new DataGridRowDragDropOptions()
{
AllowedEffects = DragDropEffects.Move,
DragSelectedRows = true,
SuppressSelectionDragFromDragHandle = true,
};
lstProfiles.RowDragDropOptions = rowDragDropOptions;
lstProfiles.RowDropHandler = new ProfileRowDropHandler(ViewModel);
_isDndRegistered = true;
}
ViewModel.ShowYesNoInteraction.RegisterHandler(async interaction =>
{
var message = interaction.Input;
var result = await UI.ShowYesNo(message);
interaction.SetOutput(result == ButtonResult.Yes);
}).DisposeWith(disposables);
ViewModel.SaveFileDialogInteraction.RegisterHandler(async interaction =>
{
var viewModel = ViewModel;
if (viewModel is null)
{
interaction.SetOutput(false);
return;
}
var profileItem = interaction.Input;
var fileName = await UI.SaveFileDialog("");
if (fileName.IsNullOrEmpty())
{
interaction.SetOutput(false);
return;
}
await viewModel.Export2ClientConfigResult(fileName, profileItem);
interaction.SetOutput(true);
}).DisposeWith(disposables);
ViewModel.SetClipboardDataInteraction.RegisterHandler(async interaction =>
{
var strData = interaction.Input;
await AvaUtils.SetClipboardData(this, strData);
interaction.SetOutput(RxVoid.Default);
}).DisposeWith(disposables);
ViewModel.ProfilesFocusInteraction.RegisterHandler(interaction =>
{
lstProfiles.Focus();
interaction.SetOutput(RxVoid.Default);
}).DisposeWith(disposables);
ViewModel.ShareServerInteraction.RegisterHandler(async interaction =>
{
var url = interaction.Input;
if (url.IsNullOrEmpty())
{
interaction.SetOutput(RxVoid.Default);
return;
}
await ShareServer(url);
interaction.SetOutput(RxVoid.Default);
}).DisposeWith(disposables);
ViewModel.DispatcherRefreshServersBizInteraction.RegisterHandler(interaction =>
{
Dispatcher.UIThread.Post(RefreshServersBiz, DispatcherPriority.Default);
interaction.SetOutput(RxVoid.Default);
}).DisposeWith(disposables);
ViewModel.AdjustMainLvColWidthInteraction.RegisterHandler(interaction =>
{
//AutofitColumnWidth();
interaction.SetOutput(RxVoid.Default);
}).DisposeWith(disposables);
AppEvents.AppExitRequested
.AsObservable()
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(_ => StorageUI())
.DisposeWith(disposables);
});
RestoreUI();
}
private async void LstProfiles_Sorting(object? sender, DataGridColumnEventArgs e)
{
e.Handled = true;
if (ViewModel != null && e.Column?.Tag?.ToString() != null)
{
await ViewModel.SortServer(e.Column.Tag.ToString());
}
e.Handled = false;
}
#region Event
public async Task ShareServer(string url)
{
if (url.IsNullOrEmpty())
{
return;
}
var dialog = new QrcodeView(url);
await DialogHost.Show(dialog);
}
public void RefreshServersBiz()
{
if (lstProfiles.SelectedIndex >= 0)
{
lstProfiles.ScrollIntoView(lstProfiles.SelectedItem, null);
}
}
private void lstProfiles_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (ViewModel != null)
{
ViewModel.SelectedProfiles = lstProfiles.SelectedItems.Cast<ProfileItemModel>().ToList();
}
}
private void LstProfiles_DoubleTapped(object? sender, Avalonia.Input.TappedEventArgs e)
{
var source = e.Source as Border;
if (source?.Name == "HeaderBackground")
{
return;
}
if (_config.UiItem.DoubleClick2Activate)
{
ViewModel?.SetDefaultServer();
}
else
{
ViewModel?.EditServerAsync();
}
}
//private void LstProfiles_ColumnHeader_Click(object? sender, RoutedEventArgs e)
//{
// var colHeader = sender as DataGridColumnHeader;
// if (colHeader == null || colHeader.TabIndex < 0 || colHeader.Column == null)
// {
// return;
// }
// var colName = ((MyDGTextColumn)colHeader.Column).ExName;
// ViewModel?.SortServer(colName);
//}
private void menuSelectAll_Click(object? sender, RoutedEventArgs e)
{
lstProfiles.SelectAll();
}
private void LstProfiles_KeyDown(object? sender, KeyEventArgs e)
{
if (e.KeyModifiers is KeyModifiers.Control or KeyModifiers.Meta)
{
switch (e.Key)
{
case Key.A:
menuSelectAll_Click(null, null);
break;
case Key.C:
ViewModel?.Export2ShareUrlAsync(false);
break;
case Key.D:
ViewModel?.EditServerAsync();
break;
case Key.F:
ViewModel?.ShareServerAsync();
break;
case Key.O:
ViewModel?.ServerSpeedtest(ESpeedActionType.Tcping);
break;
case Key.R:
ViewModel?.ServerSpeedtest(ESpeedActionType.Realping);
break;
case Key.T:
ViewModel?.ServerSpeedtest(ESpeedActionType.Speedtest);
break;
case Key.E:
ViewModel?.ServerSpeedtest(ESpeedActionType.Mixedtest);
break;
}
}
else
{
switch (e.Key)
{
case Key.Enter:
//case Key.Return:
ViewModel?.SetDefaultServer();
break;
case Key.Delete:
case Key.Back:
ViewModel?.RemoveServerAsync();
break;
case Key.T:
ViewModel?.MoveServer(EMove.Top);
break;
case Key.U:
ViewModel?.MoveServer(EMove.Up);
break;
case Key.D:
ViewModel?.MoveServer(EMove.Down);
break;
case Key.B:
ViewModel?.MoveServer(EMove.Bottom);
break;
case Key.Escape:
ViewModel?.ServerSpeedtestStop();
break;
}
}
}
private void BtnAutofitColumnWidth_Click(object? sender, RoutedEventArgs e)
{
AutofitColumnWidth();
}
private void AutofitColumnWidth()
{
try
{
//First scroll horizontally to the initial position to avoid the control crash bug
if (lstProfiles.SelectedIndex >= 0)
{
lstProfiles.ScrollIntoView(lstProfiles.SelectedItem, lstProfiles.Columns[0]);
}
else
{
var model = lstProfiles.ItemsSource.Cast<ProfileItemModel>();
if (model.Any())
{
lstProfiles.ScrollIntoView(model.First(), lstProfiles.Columns[0]);
}
else
{
return;
}
}
foreach (var it in lstProfiles.Columns)
{
it.Width = new DataGridLength(1, DataGridLengthUnitType.Auto);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
private void TxtServerFilter_KeyDown(object? sender, KeyEventArgs e)
{
if (e.Key is Key.Enter or Key.Return)
{
ViewModel?.RefreshServers();
}
}
#endregion Event
#region UI
private void RestoreUI()
{
try
{
var lvColumnItem = _config.UiItem.MainColumnItem.OrderBy(t => t.Index).ToList();
var displayIndex = 0;
foreach (var item in lvColumnItem)
{
foreach (var item2 in lstProfiles.Columns)
{
if (item2.Tag == null)
{
continue;
}
if (item2.Tag.Equals(item.Name))
{
if (item.Width < 0)
{
item2.IsVisible = false;
}
else
{
item2.Width = new DataGridLength(item.Width, DataGridLengthUnitType.Pixel);
item2.DisplayIndex = displayIndex++;
}
if (item.Name.StartsWith("to", StringComparison.CurrentCultureIgnoreCase))
{
item2.IsVisible = _config.GuiItem.EnableStatistics;
}
if (item.Name.Equals("IpInfo", StringComparison.CurrentCultureIgnoreCase))
{
item2.IsVisible = _config.SpeedTestItem.IPAPIUrl.IsNotEmpty() && !_config.UiItem.HideColumnIpInfo;
}
}
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
private void StorageUI()
{
try
{
List<ColumnItem> lvColumnItem = [];
foreach (var item2 in lstProfiles.Columns)
{
if (item2.Tag == null)
{
continue;
}
lvColumnItem.Add(new()
{
Name = (string)item2.Tag,
Width = (int)(item2.IsVisible == true ? item2.ActualWidth : -1),
Index = item2.DisplayIndex
});
}
_config.UiItem.MainColumnItem = lvColumnItem;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
#endregion UI
//#region Drag and Drop
//private static readonly DataFormat<ProfileItemModel> LstProfilesRowFormat =
// DataFormat.CreateInProcessFormat<ProfileItemModel>("LstProfilesRow");
//private (Point, PointerPressedEventArgs)? _dragStartPoint;
//private void LstProfiles_PointerPressed(object? sender, PointerPressedEventArgs e)
//{
// var properties = e.GetCurrentPoint(this).Properties;
// if (properties.IsLeftButtonPressed)
// {
// _dragStartPoint = (e.GetPosition(this), e);
// }
//}
//private async void LstProfiles_PointerMoved(object? sender, PointerEventArgs e)
//{
// try
// {
// if (_dragStartPoint == null)
// {
// return;
// }
// var properties = e.GetCurrentPoint(this).Properties;
// if (!properties.IsLeftButtonPressed)
// {
// _dragStartPoint = null;
// return;
// }
// var currentPoint = e.GetPosition(this);
// var startPoint = _dragStartPoint.Value.Item1;
// var delta = startPoint - currentPoint;
// var threshold = new Vector(4, 4);
// if (!(Math.Abs(delta.X) >= threshold.X) && !(Math.Abs(delta.Y) >= threshold.Y))
// {
// return;
// }
// var dragStartEventArgs = _dragStartPoint.Value.Item2;
// _dragStartPoint = null;
// if (e.Source is not Visual visualSource)
// {
// return;
// }
// var row = visualSource.FindAncestorOfType<DataGridRow>(true);
// if (row?.DataContext == null)
// {
// return;
// }
// e.Handled = true;
// var dragData = new DataTransfer();
// var item = DataTransferItem.Create(LstProfilesRowFormat, row.DataContext as ProfileItemModel);
// dragData.Add(item);
// await DragDrop.DoDragDropAsync(dragStartEventArgs, dragData, DragDropEffects.Move);
// }
// catch
// {
// // Ignore
// }
//}
//private void LstProfiles_PointerReleased(object? sender, PointerReleasedEventArgs e)
//{
// _dragStartPoint = null;
//}
//private void LstProfiles_DragOver(object? sender, DragEventArgs e)
//{
// if (!e.DataTransfer.Contains(LstProfilesRowFormat))
// {
// e.DragEffects = DragDropEffects.None;
// return;
// }
// e.DragEffects = DragDropEffects.Move;
//}
//private void LstProfiles_Drop(object? sender, DragEventArgs e)
//{
// if (!e.DataTransfer.Contains(LstProfilesRowFormat))
// {
// return;
// }
// ProfileItemModel? sourceItem = null;
// foreach (var item in e.DataTransfer.Items)
// {
// if (!item.Formats.Contains(LstProfilesRowFormat))
// {
// continue;
// }
// if (item.TryGetRaw(LstProfilesRowFormat) is not ProfileItemModel model)
// {
// continue;
// }
// sourceItem = model;
// break;
// }
// if (sourceItem == null)
// {
// return;
// }
// if (e.Source is not Visual visualTarget)
// {
// return;
// }
// var targetRow = visualTarget.FindAncestorOfType<DataGridRow>(true);
// if (targetRow is not { DataContext: ProfileItemModel targetItem })
// {
// return;
// }
// if (ReferenceEquals(sourceItem, targetItem))
// {
// return;
// }
// if (lstProfiles.ItemsSource is not IList<ProfileItemModel> items)
// {
// return;
// }
// var oldIndex = items.IndexOf(sourceItem);
// var newIndex = items.IndexOf(targetItem);
// if (oldIndex >= 0 && newIndex >= 0)
// {
// ViewModel?.MoveServerTo(oldIndex, targetItem);
// }
//}
//#endregion Drag and Drop
private sealed class ProfileRowDropHandler(ProfilesViewModel viewModel) : IDataGridRowDropHandler
{
private readonly DataGridRowReorderHandler _reorderHandler = new();
public bool Validate(DataGridRowDropEventArgs args)
{
ArgumentNullException.ThrowIfNull(args);
return _reorderHandler.Validate(args);
}
public bool Execute(DataGridRowDropEventArgs args)
{
ArgumentNullException.ThrowIfNull(args);
if (!_reorderHandler.Execute(args))
{
return false;
}
if (viewModel?.ProfileItems != null)
{
for (var index = 0; index < viewModel.ProfileItems.Count; index++)
{
var item = viewModel.ProfileItems[index];
var sort = (index + 1) * 10;
item.Sort = sort;
ProfileExManager.Instance.SetSort(item.IndexId, sort);
}
}
return true;
}
}
}