Compare commits

...

3 commits

Author SHA1 Message Date
DHR60
ef5fee9975
Fix (#8909)
Some checks failed
release Linux / build (Release) (push) Has been cancelled
release macOS / build (Release) (push) Has been cancelled
release Windows desktop (Avalonia UI) / build (Release) (push) Has been cancelled
release Windows / build (Release) (push) Has been cancelled
release Linux / rpm (push) Has been cancelled
2026-03-08 19:00:27 +08:00
2dust
df800a60c2 Persist DataGrid column layout for ClashConnectionsView 2026-03-08 18:59:56 +08:00
2dust
679bd8afcc Persist DataGrid column layout for ClashConnectionsView
https://github.com/2dust/v2rayN/issues/8893
2026-03-08 17:57:09 +08:00
9 changed files with 282 additions and 74 deletions

View file

@ -269,6 +269,7 @@ public class CoreConfigContextBuilder
IndexId = $"inner-{Utils.GetGuid(false)}", IndexId = $"inner-{Utils.GetGuid(false)}",
ConfigType = EConfigType.ProxyChain, ConfigType = EConfigType.ProxyChain,
CoreType = AppManager.Instance.GetCoreType(node, node.ConfigType), CoreType = AppManager.Instance.GetCoreType(node, node.ConfigType),
Remarks = node.Remarks,
}; };
List<string?> childItems = [prevNode?.IndexId, node.IndexId, nextNode?.IndexId]; List<string?> childItems = [prevNode?.IndexId, node.IndexId, nextNode?.IndexId];
var chainExtraItem = chainNode.GetProtocolExtra() with var chainExtraItem = chainNode.GetProtocolExtra() with

View file

@ -154,6 +154,7 @@ public static class ConfigHandler
DownMbps = 100 DownMbps = 100
}; };
config.ClashUIItem ??= new(); config.ClashUIItem ??= new();
config.ClashUIItem.ConnectionsColumnItem ??= new();
config.SystemProxyItem ??= new(); config.SystemProxyItem ??= new();
config.WebDavItem ??= new(); config.WebDavItem ??= new();
config.CheckUpdateItem ??= new(); config.CheckUpdateItem ??= new();

View file

@ -208,6 +208,7 @@ public class ClashUIItem
public int ProxiesAutoDelayTestInterval { get; set; } = 10; public int ProxiesAutoDelayTestInterval { get; set; } = 10;
public bool ConnectionsAutoRefresh { get; set; } public bool ConnectionsAutoRefresh { get; set; }
public int ConnectionsRefreshInterval { get; set; } = 2; public int ConnectionsRefreshInterval { get; set; } = 2;
public List<ColumnItem> ConnectionsColumnItem { get; set; }
} }
[Serializable] [Serializable]

View file

@ -74,23 +74,28 @@
<DataGridTextColumn <DataGridTextColumn
Width="300" Width="300"
Binding="{Binding Host}" Binding="{Binding Host}"
Header="{x:Static resx:ResUI.TbSortingHost}" /> Header="{x:Static resx:ResUI.TbSortingHost}"
Tag="Host" />
<DataGridTextColumn <DataGridTextColumn
Width="500" Width="500"
Binding="{Binding Chain}" Binding="{Binding Chain}"
Header="{x:Static resx:ResUI.TbSortingChain}" /> Header="{x:Static resx:ResUI.TbSortingChain}"
Tag="Chain" />
<DataGridTextColumn <DataGridTextColumn
Width="80" Width="80"
Binding="{Binding Network}" Binding="{Binding Network}"
Header="{x:Static resx:ResUI.TbSortingNetwork}" /> Header="{x:Static resx:ResUI.TbSortingNetwork}"
Tag="Network" />
<DataGridTextColumn <DataGridTextColumn
Width="160" Width="160"
Binding="{Binding Type}" Binding="{Binding Type}"
Header="{x:Static resx:ResUI.TbSortingType}" /> Header="{x:Static resx:ResUI.TbSortingType}"
Tag="Type" />
<DataGridTextColumn <DataGridTextColumn
Width="100" Width="100"
Binding="{Binding Elapsed}" Binding="{Binding Elapsed}"
Header="{x:Static resx:ResUI.TbSortingTime}" /> Header="{x:Static resx:ResUI.TbSortingTime}"
Tag="Elapsed" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</DockPanel> </DockPanel>

View file

@ -2,9 +2,15 @@ namespace v2rayN.Desktop.Views;
public partial class ClashConnectionsView : ReactiveUserControl<ClashConnectionsViewModel> public partial class ClashConnectionsView : ReactiveUserControl<ClashConnectionsViewModel>
{ {
private static Config _config;
private static readonly string _tag = "ClashConnectionsView";
public ClashConnectionsView() public ClashConnectionsView()
{ {
InitializeComponent(); InitializeComponent();
_config = AppManager.Instance.Config;
ViewModel = new ClashConnectionsViewModel(UpdateViewHandler); ViewModel = new ClashConnectionsViewModel(UpdateViewHandler);
btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click; btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click;
@ -19,7 +25,15 @@ public partial class ClashConnectionsView : ReactiveUserControl<ClashConnections
this.Bind(ViewModel, vm => vm.HostFilter, v => v.txtHostFilter.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.HostFilter, v => v.txtHostFilter.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.btnConnectionCloseAll).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.btnConnectionCloseAll).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
AppEvents.AppExitRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ => StorageUI())
.DisposeWith(disposables);
}); });
RestoreUI();
} }
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj) private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
@ -51,4 +65,74 @@ public partial class ClashConnectionsView : ReactiveUserControl<ClashConnections
{ {
ViewModel?.ClashConnectionClose(false); ViewModel?.ClashConnectionClose(false);
} }
#region UI
private void RestoreUI()
{
try
{
var lvColumnItem = _config.ClashUIItem?.ConnectionsColumnItem?.OrderBy(t => t.Index).ToList();
if (lvColumnItem == null)
{
return;
}
var displayIndex = 0;
foreach (var item in lvColumnItem)
{
foreach (var item2 in lstConnections.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++;
}
}
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
private void StorageUI()
{
try
{
List<ColumnItem> lvColumnItem = new();
foreach (var item2 in lstConnections.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.ClashUIItem.ConnectionsColumnItem = lvColumnItem;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
#endregion UI
} }

View file

@ -7,6 +7,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
{ {
private static Config _config; private static Config _config;
private Window? _window; private Window? _window;
private static readonly string _tag = "ProfilesView";
public ProfilesView() public ProfilesView()
{ {
@ -381,7 +382,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.SaveLog("ProfilesView", ex); Logging.SaveLog(_tag, ex);
} }
} }
@ -399,53 +400,67 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
private void RestoreUI() private void RestoreUI()
{ {
var lvColumnItem = _config.UiItem.MainColumnItem.OrderBy(t => t.Index).ToList(); try
var displayIndex = 0;
foreach (var item in lvColumnItem)
{ {
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.ToLower().StartsWith("to"))
{
item2.IsVisible = _config.GuiItem.EnableStatistics;
}
}
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
private void StorageUI()
{
try
{
List<ColumnItem> lvColumnItem = new();
foreach (var item2 in lstProfiles.Columns) foreach (var item2 in lstProfiles.Columns)
{ {
if (item2.Tag == null) if (item2.Tag == null)
{ {
continue; continue;
} }
if (item2.Tag.Equals(item.Name)) lvColumnItem.Add(new()
{ {
if (item.Width < 0) Name = (string)item2.Tag,
{ Width = (int)(item2.IsVisible == true ? item2.ActualWidth : -1),
item2.IsVisible = false; Index = item2.DisplayIndex
} });
else
{
item2.Width = new DataGridLength(item.Width, DataGridLengthUnitType.Pixel);
item2.DisplayIndex = displayIndex++;
}
if (item.Name.ToLower().StartsWith("to"))
{
item2.IsVisible = _config.GuiItem.EnableStatistics;
}
}
} }
_config.UiItem.MainColumnItem = lvColumnItem;
} }
} catch (Exception ex)
private void StorageUI()
{
List<ColumnItem> lvColumnItem = new();
foreach (var item2 in lstProfiles.Columns)
{ {
if (item2.Tag == null) Logging.SaveLog(_tag, ex);
{
continue;
}
lvColumnItem.Add(new()
{
Name = (string)item2.Tag,
Width = (int)(item2.IsVisible == true ? item2.ActualWidth : -1),
Index = item2.DisplayIndex
});
} }
_config.UiItem.MainColumnItem = lvColumnItem;
} }
#endregion UI #endregion UI

View file

@ -2,6 +2,7 @@
x:Class="v2rayN.Views.ClashConnectionsView" x:Class="v2rayN.Views.ClashConnectionsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:base="clr-namespace:v2rayN.Base"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -78,25 +79,30 @@
</ContextMenu> </ContextMenu>
</DataGrid.ContextMenu> </DataGrid.ContextMenu>
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn <base:MyDGTextColumn
Width="300" Width="300"
Binding="{Binding Host}" Binding="{Binding Host}"
ExName="Host"
Header="{x:Static resx:ResUI.TbSortingHost}" /> Header="{x:Static resx:ResUI.TbSortingHost}" />
<DataGridTextColumn <base:MyDGTextColumn
Width="500" Width="500"
Binding="{Binding Chain}" Binding="{Binding Chain}"
ExName="Chain"
Header="{x:Static resx:ResUI.TbSortingChain}" /> Header="{x:Static resx:ResUI.TbSortingChain}" />
<DataGridTextColumn <base:MyDGTextColumn
Width="80" Width="80"
Binding="{Binding Network}" Binding="{Binding Network}"
ExName="Network"
Header="{x:Static resx:ResUI.TbSortingNetwork}" /> Header="{x:Static resx:ResUI.TbSortingNetwork}" />
<DataGridTextColumn <base:MyDGTextColumn
Width="160" Width="160"
Binding="{Binding Type}" Binding="{Binding Type}"
ExName="Type"
Header="{x:Static resx:ResUI.TbSortingType}" /> Header="{x:Static resx:ResUI.TbSortingType}" />
<DataGridTextColumn <base:MyDGTextColumn
Width="100" Width="100"
Binding="{Binding Elapsed}" Binding="{Binding Elapsed}"
ExName="Elapsed"
Header="{x:Static resx:ResUI.TbSortingTime}" /> Header="{x:Static resx:ResUI.TbSortingTime}" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>

View file

@ -1,4 +1,5 @@
using System.Windows.Controls; using System.Windows.Controls;
using v2rayN.Base;
namespace v2rayN.Views; namespace v2rayN.Views;
@ -7,9 +8,14 @@ namespace v2rayN.Views;
/// </summary> /// </summary>
public partial class ClashConnectionsView public partial class ClashConnectionsView
{ {
private static Config _config;
private static readonly string _tag = "ClashConnectionsView";
public ClashConnectionsView() public ClashConnectionsView()
{ {
InitializeComponent(); InitializeComponent();
_config = AppManager.Instance.Config;
ViewModel = new ClashConnectionsViewModel(UpdateViewHandler); ViewModel = new ClashConnectionsViewModel(UpdateViewHandler);
btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click; btnAutofitColumnWidth.Click += BtnAutofitColumnWidth_Click;
@ -24,7 +30,15 @@ public partial class ClashConnectionsView
this.Bind(ViewModel, vm => vm.HostFilter, v => v.txtHostFilter.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.HostFilter, v => v.txtHostFilter.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.btnConnectionCloseAll).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.ConnectionCloseAllCmd, v => v.btnConnectionCloseAll).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
AppEvents.AppExitRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ => StorageUI())
.DisposeWith(disposables);
}); });
RestoreUI();
} }
private async Task<bool> UpdateViewHandler(EViewAction action, object? obj) private async Task<bool> UpdateViewHandler(EViewAction action, object? obj)
@ -48,7 +62,7 @@ public partial class ClashConnectionsView
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.SaveLog("ClashConnectionsView", ex); Logging.SaveLog(_tag, ex);
} }
} }
@ -56,4 +70,71 @@ public partial class ClashConnectionsView
{ {
ViewModel?.ClashConnectionClose(false); ViewModel?.ClashConnectionClose(false);
} }
#region UI
private void RestoreUI()
{
try
{
var lvColumnItem = _config.ClashUIItem?.ConnectionsColumnItem?.OrderBy(t => t.Index).ToList();
if (lvColumnItem == null)
{
return;
}
var displayIndex = 0;
foreach (var item in lvColumnItem)
{
foreach (var col in lstConnections.Columns.Cast<MyDGTextColumn>())
{
if (col.ExName == item.Name)
{
if (item.Width > 0)
{
col.Width = item.Width;
}
col.DisplayIndex = displayIndex++;
break;
}
}
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
private void StorageUI()
{
try
{
List<ColumnItem> lvColumnItem = new();
foreach (var col in lstConnections.Columns.Cast<MyDGTextColumn>())
{
var name = col.ExName;
if (string.IsNullOrWhiteSpace(name))
{
continue;
}
lvColumnItem.Add(new()
{
Name = name,
Width = (int)col.ActualWidth,
Index = col.DisplayIndex
});
}
_config.ClashUIItem.ConnectionsColumnItem = lvColumnItem;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
#endregion UI
} }

View file

@ -10,6 +10,7 @@ namespace v2rayN.Views;
public partial class ProfilesView public partial class ProfilesView
{ {
private static Config _config; private static Config _config;
private static readonly string _tag = "ProfilesView";
public ProfilesView() public ProfilesView()
{ {
@ -339,7 +340,7 @@ public partial class ProfilesView
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.SaveLog("ProfilesView", ex); Logging.SaveLog(_tag, ex);
} }
} }
@ -357,46 +358,59 @@ public partial class ProfilesView
private void RestoreUI() private void RestoreUI()
{ {
var lvColumnItem = _config.UiItem.MainColumnItem.OrderBy(t => t.Index).ToList(); try
var displayIndex = 0;
foreach (var item in lvColumnItem)
{ {
foreach (MyDGTextColumn item2 in lstProfiles.Columns) var lvColumnItem = _config.UiItem.MainColumnItem.OrderBy(t => t.Index).ToList();
var displayIndex = 0;
foreach (var item in lvColumnItem)
{ {
if (item2.ExName == item.Name) foreach (var item2 in lstProfiles.Columns.Cast<MyDGTextColumn>())
{ {
if (item.Width < 0) if (item2.ExName == item.Name)
{ {
item2.Visibility = Visibility.Hidden; if (item.Width < 0)
} {
else item2.Visibility = Visibility.Hidden;
{ }
item2.Width = item.Width; else
item2.DisplayIndex = displayIndex++; {
} item2.Width = item.Width;
if (item.Name.ToLower().StartsWith("to")) item2.DisplayIndex = displayIndex++;
{ }
item2.Visibility = _config.GuiItem.EnableStatistics ? Visibility.Visible : Visibility.Hidden; if (item.Name.ToLower().StartsWith("to"))
{
item2.Visibility = _config.GuiItem.EnableStatistics ? Visibility.Visible : Visibility.Hidden;
}
} }
} }
} }
} }
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
} }
private void StorageUI() private void StorageUI()
{ {
List<ColumnItem> lvColumnItem = new(); try
foreach (var t in lstProfiles.Columns)
{ {
var item2 = (MyDGTextColumn)t; List<ColumnItem> lvColumnItem = new();
lvColumnItem.Add(new() foreach (var item2 in lstProfiles.Columns.Cast<MyDGTextColumn>())
{ {
Name = item2.ExName, lvColumnItem.Add(new()
Width = (int)(item2.Visibility == Visibility.Visible ? item2.ActualWidth : -1), {
Index = item2.DisplayIndex Name = item2.ExName,
}); Width = (int)(item2.Visibility == Visibility.Visible ? item2.ActualWidth : -1),
Index = item2.DisplayIndex
});
}
_config.UiItem.MainColumnItem = lvColumnItem;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
} }
_config.UiItem.MainColumnItem = lvColumnItem;
} }
#endregion UI #endregion UI
@ -405,7 +419,7 @@ public partial class ProfilesView
private Point startPoint = new(); private Point startPoint = new();
private int startIndex = -1; private int startIndex = -1;
private string formatData = "ProfileItemModel"; private readonly string formatData = "ProfileItemModel";
/// <summary> /// <summary>
/// Helper to search up the VisualTree /// Helper to search up the VisualTree