pull/7891/head
DHR60 2025-09-06 18:50:07 +08:00
parent 9a4d8faaa2
commit 1242011513
6 changed files with 327 additions and 3 deletions

View File

@ -0,0 +1,118 @@
<Window
x:Class="v2rayN.Desktop.Views.ProfilesSelectWindow"
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"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
xmlns:conv="using:v2rayN.Desktop.Converters"
x:DataType="vms:ProfilesSelectViewModel"
Width="800"
Height="450"
Title="{x:Static resx:ResUI.TbSelectProfile}"
mc:Ignorable="d">
<Window.Resources>
<conv:DelayColorConverter x:Key="DelayColorConverter" />
</Window.Resources>
<DockPanel Margin="8">
<!-- Bottom buttons -->
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Center" Margin="4">
<Button x:Name="btnSave" Width="100" Content="{x:Static resx:ResUI.TbConfirm}" />
<Button x:Name="btnCancel" Width="100" Margin="8,0" Content="{x:Static resx:ResUI.TbCancel}" />
</StackPanel>
<Grid>
<DockPanel>
<!-- Top tools -->
<WrapPanel DockPanel.Dock="Top" Margin="4">
<ListBox
x:Name="lstGroup"
Margin="4,0"
DisplayMemberBinding="{Binding Remarks}"
ItemsSource="{Binding SubItems}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
<Button
x:Name="btnAutofitColumnWidth"
Width="32"
Height="32"
Margin="8,0"
ToolTip.Tip="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}">
<Button.Content>
<PathIcon Data="{StaticResource building_fit}" />
</Button.Content>
</Button>
<TextBox
x:Name="txtServerFilter"
Width="200"
Margin="8,0"
VerticalContentAlignment="Center"
Watermark="{x:Static resx:ResUI.MsgServerTitle}"
Text="{Binding ServerFilter, Mode=TwoWay}" />
</WrapPanel>
<!-- Profiles grid -->
<DataGrid
x:Name="lstProfiles"
AutoGenerateColumns="False"
BorderThickness="1"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
GridLinesVisibility="All"
HeadersVisibility="All"
IsReadOnly="True"
ItemsSource="{Binding ProfileItems}">
<DataGrid.Columns>
<DataGridTextColumn Width="80" Binding="{Binding ConfigType}" Header="{x:Static resx:ResUI.LvServiceType}" Tag="ConfigType" />
<DataGridTemplateColumn Tag="Remarks">
<DataGridTemplateColumn.Header>
<TextBlock Text="{x:Static resx:ResUI.LvRemarks}" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="8,0">
<TextBlock Text="{Binding Remarks}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="120" Binding="{Binding Address}" Header="{x:Static resx:ResUI.LvAddress}" Tag="Address" />
<DataGridTextColumn Width="60" Binding="{Binding Port}" Header="{x:Static resx:ResUI.LvPort}" Tag="Port" />
<DataGridTextColumn Width="100" Binding="{Binding Network}" Header="{x:Static resx:ResUI.LvTransportProtocol}" Tag="Network" />
<DataGridTextColumn Width="100" Binding="{Binding StreamSecurity}" Header="{x:Static resx:ResUI.LvTLS}" Tag="StreamSecurity" />
<DataGridTextColumn Width="100" Binding="{Binding SubRemarks}" Header="{x:Static resx:ResUI.LvSubscription}" Tag="SubRemarks" />
<DataGridTemplateColumn SortMemberPath="Delay" Tag="DelayVal">
<DataGridTemplateColumn.Header>
<TextBlock Text="{x:Static resx:ResUI.LvTestDelay}" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Margin="8,0" HorizontalAlignment="Right" VerticalAlignment="Center" Foreground="{Binding Delay, Converter={StaticResource DelayColorConverter}}" Text="{Binding DelayVal}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="100" Binding="{Binding SpeedVal}" Header="{x:Static resx:ResUI.LvTestSpeed}" Tag="SpeedVal" />
<DataGridTextColumn Width="100" Binding="{Binding TodayUp}" Header="{x:Static resx:ResUI.LvTodayUploadDataAmount}" Tag="TodayUp" />
<DataGridTextColumn Width="100" Binding="{Binding TodayDown}" Header="{x:Static resx:ResUI.LvTodayDownloadDataAmount}" Tag="TodayDown" />
<DataGridTextColumn Width="100" Binding="{Binding TotalUp}" Header="{x:Static resx:ResUI.LvTotalUploadDataAmount}" Tag="TotalUp" />
<DataGridTextColumn Width="100" Binding="{Binding TotalDown}" Header="{x:Static resx:ResUI.LvTotalDownloadDataAmount}" Tag="TotalDown" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Grid>
</DockPanel>
</Window>

View File

@ -0,0 +1,141 @@
using System.Linq;
using System.Reactive.Disposables;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using ReactiveUI;
using ServiceLib.Manager;
using v2rayN.Desktop.Common;
namespace v2rayN.Desktop.Views;
public partial class ProfilesSelectWindow : ReactiveWindow<ProfilesSelectViewModel>
{
private static Config _config;
public Task<ProfileItem?> ProfileItem => GetFirstProfileItemAsync();
public ProfilesSelectWindow()
{
InitializeComponent();
_config = AppManager.Instance.Config;
btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click;
txtServerFilter.KeyDown += TxtServerFilter_KeyDown;
lstProfiles.KeyDown += LstProfiles_KeyDown;
lstProfiles.SelectionChanged += LstProfiles_SelectionChanged;
lstProfiles.LoadingRow += LstProfiles_LoadingRow;
lstProfiles.Sorting += LstProfiles_Sorting;
lstProfiles.DoubleTapped += LstProfiles_DoubleTapped;
ViewModel = new ProfilesSelectViewModel(UpdateViewHandler);
DataContext = ViewModel;
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.Bind(ViewModel, vm => vm.SelectedSub, v => v.lstGroup.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ServerFilter, v => v.txtServerFilter.Text).DisposeWith(disposables);
});
btnSave.Click += (s, e) => ViewModel?.SelectFinish();
btnCancel.Click += (s, e) => Close(false);
}
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
{
switch (action)
{
case EViewAction.CloseWindow:
Close(true);
break;
}
return await Task.FromResult(true);
}
private void LstProfiles_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (ViewModel != null)
{
ViewModel.SelectedProfiles = lstProfiles.SelectedItems.Cast<ProfileItemModel>().ToList();
}
}
private void LstProfiles_LoadingRow(object? sender, DataGridRowEventArgs e)
{
e.Row.Header = $" {e.Row.Index + 1}";
}
private void LstProfiles_DoubleTapped(object? sender, TappedEventArgs e)
{
ViewModel?.SelectFinish();
}
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;
}
private void LstProfiles_KeyDown(object? sender, KeyEventArgs e)
{
if (e.KeyModifiers is KeyModifiers.Control or KeyModifiers.Meta)
{
if (e.Key == Key.A)
{
lstProfiles.SelectAll();
}
}
else
{
if (e.Key is Key.Enter or Key.Return)
{
ViewModel?.SelectFinish();
}
}
}
private void BtnAutofitColumnWidth_Click(object? sender, RoutedEventArgs e)
{
AutofitColumnWidth();
}
private void AutofitColumnWidth()
{
try
{
foreach (var col in lstProfiles.Columns)
{
col.Width = new DataGridLength(1, DataGridLengthUnitType.Auto);
}
}
catch
{
}
}
private void TxtServerFilter_KeyDown(object? sender, KeyEventArgs e)
{
if (e.Key is Key.Enter or Key.Return)
{
ViewModel?.RefreshServers();
}
}
public async Task<ProfileItem?> GetFirstProfileItemAsync()
{
var item = await ViewModel?.GetProfileItem();
return item;
}
}

View File

@ -54,13 +54,22 @@
Width="300"
Margin="{StaticResource Margin4}"
Text="{Binding SelectedSource.OutboundTag, Mode=TwoWay}" />
<TextBlock
<StackPanel
Grid.Row="1"
Grid.Column="2"
Margin="{StaticResource Margin4}"
Orientation="Horizontal"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}" />
VerticalAlignment="Center">
<Button
x:Name="btnSelectProfile"
Margin="0,0,8,0"
Content="{x:Static resx:ResUI.TbSelectProfile}"
Click="BtnSelectProfile_Click" />
<TextBlock
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbRuleOutboundTagTip}" />
</StackPanel>
<TextBlock
Grid.Row="2"

View File

@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Interactivity;
using ReactiveUI;
using v2rayN.Desktop.Base;
using System.Threading.Tasks;
namespace v2rayN.Desktop.Views;
@ -93,4 +94,18 @@ public partial class RoutingRuleDetailsWindow : WindowBase<RoutingRuleDetailsVie
{
ProcUtils.ProcessStart("https://xtls.github.io/config/routing.html#ruleobject");
}
private async void BtnSelectProfile_Click(object? sender, RoutedEventArgs e)
{
var selectWindow = new ProfilesSelectWindow();
var result = await selectWindow.ShowDialog<bool?>(this);
if (result == true)
{
var profile = await selectWindow.ProfileItem;
if (profile != null)
{
cmbOutboundTag.Text = profile.Remarks;
}
}
}
}

View File

@ -204,6 +204,12 @@
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Watermark="{x:Static resx:ResUI.LvPrevProfileTip}" />
<Button
Grid.Row="9"
Grid.Column="2"
Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.TbSelectProfile}"
Click="BtnSelectPrevProfile_Click" />
<TextBlock
Grid.Row="10"
@ -218,6 +224,12 @@
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Watermark="{x:Static resx:ResUI.LvPrevProfileTip}" />
<Button
Grid.Row="10"
Grid.Column="2"
Margin="{StaticResource Margin4}"
Content="{x:Static resx:ResUI.TbSelectProfile}"
Click="BtnSelectNextProfile_Click" />
<TextBlock
Grid.Row="11"

View File

@ -3,6 +3,7 @@ using Avalonia;
using Avalonia.Interactivity;
using ReactiveUI;
using v2rayN.Desktop.Base;
using System.Threading.Tasks;
namespace v2rayN.Desktop.Views;
@ -59,4 +60,32 @@ public partial class SubEditWindow : WindowBase<SubEditViewModel>
{
txtRemarks.Focus();
}
private async void BtnSelectPrevProfile_Click(object? sender, RoutedEventArgs e)
{
var selectWindow = new ProfilesSelectWindow();
var result = await selectWindow.ShowDialog<bool?>(this);
if (result == true)
{
var profile = await selectWindow.ProfileItem;
if (profile != null)
{
txtPrevProfile.Text = profile.Remarks;
}
}
}
private async void BtnSelectNextProfile_Click(object? sender, RoutedEventArgs e)
{
var selectWindow = new ProfilesSelectWindow();
var result = await selectWindow.ShowDialog<bool?>(this);
if (result == true)
{
var profile = await selectWindow.ProfileItem;
if (profile != null)
{
txtNextProfile.Text = profile.Remarks;
}
}
}
}