optimize<*>: use custom view locator instead of ContentControl.DataTemplates to avoid memory leak.

This commit is contained in:
leo 2024-03-02 22:45:14 +08:00
parent 27d4dd5f64
commit 60e664ab26
9 changed files with 77 additions and 62 deletions

View file

@ -160,26 +160,23 @@ namespace SourceGit.ViewModels {
} else { } else {
page = ActivePage; page = ActivePage;
page.Node = node; page.Node = node;
page.View = new Views.Repository() { DataContext = repo }; page.Data = repo;
} }
} else { } else {
page.Node = node; page.Node = node;
page.View = new Views.Repository() { DataContext = repo }; page.Data = repo;
} }
ActivePage = page; ActivePage = page;
} }
private void CloseRepositoryInTab(LauncherPage page) { private void CloseRepositoryInTab(LauncherPage page) {
if (page.Node.IsRepository) { if (page.Data is Repository repo) {
var repo = Preference.FindRepository(page.Node.Id);
if (repo != null) {
Commands.AutoFetch.RemoveRepository(repo.FullPath); Commands.AutoFetch.RemoveRepository(repo.FullPath);
repo.Close(); repo.Close();
} }
}
page.View = null; page.Data = null;
} }
private LauncherPage _activePage = null; private LauncherPage _activePage = null;

View file

@ -8,9 +8,9 @@ namespace SourceGit.ViewModels {
set => SetProperty(ref _node, value); set => SetProperty(ref _node, value);
} }
public object View { public object Data {
get => _view; get => _data;
set => SetProperty(ref _view, value); set => SetProperty(ref _data, value);
} }
public AvaloniaList<Models.Notification> Notifications { public AvaloniaList<Models.Notification> Notifications {
@ -25,12 +25,12 @@ namespace SourceGit.ViewModels {
Bookmark = 0, Bookmark = 0,
IsRepository = false, IsRepository = false,
}; };
_view = new Views.Welcome() { DataContext = new Welcome() }; _data = new Welcome();
} }
public LauncherPage(RepositoryNode node, Repository repo) { public LauncherPage(RepositoryNode node, Repository repo) {
_node = node; _node = node;
_view = new Views.Repository() { DataContext = repo }; _data = repo;
} }
public override string GetId() { public override string GetId() {
@ -48,6 +48,6 @@ namespace SourceGit.ViewModels {
} }
private RepositoryNode _node = null; private RepositoryNode _node = null;
private object _view = null; private object _data = null;
} }
} }

View file

@ -1,7 +1,6 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using System; using System;
@ -208,11 +207,6 @@ namespace SourceGit.Views {
InitializeComponent(); InitializeComponent();
} }
protected override void OnUnloaded(RoutedEventArgs e) {
base.OnUnloaded(e);
GC.Collect();
}
private void OnCommitDataGridLayoutUpdated(object sender, EventArgs e) { private void OnCommitDataGridLayoutUpdated(object sender, EventArgs e) {
commitGraph.InvalidateVisual(); commitGraph.InvalidateVisual();
} }

View file

@ -223,8 +223,8 @@
</Border> </Border>
</Grid> </Grid>
<!-- Page container --> <!-- Page body -->
<ContentControl Grid.Row="1" Background="{DynamicResource Brush.ToolBar}" Content="{Binding ActivePage.View}"/> <v:LauncherBody Grid.Row="1" Background="{DynamicResource Brush.ToolBar}" Data="{Binding ActivePage.Data}"/>
<!-- Popup container --> <!-- Popup container -->
<Grid Grid.Row="1" x:Name="popupContainer" Margin="0,36,0,0" ClipToBounds="True" IsVisible="{Binding ActivePage.Popup, Converter={x:Static ObjectConverters.IsNotNull}}"> <Grid Grid.Row="1" x:Name="popupContainer" Margin="0,36,0,0" ClipToBounds="True" IsVisible="{Binding ActivePage.Popup, Converter={x:Static ObjectConverters.IsNotNull}}">

View file

@ -23,6 +23,34 @@ namespace SourceGit.Views {
} }
} }
public class LauncherBody : Border {
public static readonly StyledProperty<object> DataProperty =
AvaloniaProperty.Register<LauncherBody, object>(nameof(Data), false);
public object Data {
get => GetValue(DataProperty);
set => SetValue(DataProperty, value);
}
protected override Type StyleKeyOverride => typeof(Border);
static LauncherBody() {
DataProperty.Changed.AddClassHandler<LauncherBody>((body, ev) => {
var data = body.Data;
if (data == null) {
body.Child = null;
} else if (data is ViewModels.Welcome) {
body.Child = new Welcome { DataContext = data };
} else if (data is ViewModels.Repository) {
body.Child = new Repository { DataContext = data };
} else {
body.Child = null;
}
});
}
}
public partial class Launcher : Window, Models.INotificationReceiver { public partial class Launcher : Window, Models.INotificationReceiver {
public Launcher() { public Launcher() {
DataContext = new ViewModels.Launcher(); DataContext = new ViewModels.Launcher();

View file

@ -447,25 +447,7 @@
<Button Grid.Column="3" Classes="flat" FontWeight="Regular" Content="{DynamicResource Text.Repository.Abort}" Height="20" Padding="8,0" Margin="4,0" Command="{Binding AbortMerge}"/> <Button Grid.Column="3" Classes="flat" FontWeight="Regular" Content="{DynamicResource Text.Repository.Abort}" Height="20" Padding="8,0" Margin="4,0" Command="{Binding AbortMerge}"/>
</Grid> </Grid>
<ContentControl Grid.Row="1" Content="{Binding SelectedView}"> <v:RepositorySubView Grid.Row="1" Data="{Binding SelectedView}"/>
<ContentControl.DataTemplates>
<DataTemplate DataType="vm:Histories">
<v:Histories/>
</DataTemplate>
<DataTemplate DataType="vm:WorkingCopy">
<v:WorkingCopy/>
</DataTemplate>
<DataTemplate DataType="vm:StashesPage">
<v:StashesPage/>
</DataTemplate>
<DataTemplate DataType="x:Double">
<Border/>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
</Grid> </Grid>
</Grid> </Grid>
</Grid> </Grid>

View file

@ -1,11 +1,45 @@
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using SourceGit.ViewModels; using SourceGit.ViewModels;
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace SourceGit.Views { namespace SourceGit.Views {
public class RepositorySubView : Border {
public static readonly StyledProperty<object> DataProperty =
AvaloniaProperty.Register<RepositorySubView, object>(nameof(Data), false);
public object Data {
get => GetValue(DataProperty);
set => SetValue(DataProperty, value);
}
protected override Type StyleKeyOverride => typeof(Border);
static RepositorySubView() {
DataProperty.Changed.AddClassHandler<RepositorySubView>((view, ev) => {
var data = view.Data;
if (data == null) {
view.Child = null;
} else if (data is ViewModels.Histories) {
view.Child = new Histories { DataContext = data };
} else if (data is ViewModels.WorkingCopy) {
view.Child = new WorkingCopy { DataContext = data };
} else if (data is ViewModels.StashesPage) {
view.Child = new StashesPage { DataContext = data };
} else {
view.Child = null;
}
GC.Collect();
});
}
}
public partial class Repository : UserControl { public partial class Repository : UserControl {
public Repository() { public Repository() {
InitializeComponent(); InitializeComponent();

View file

@ -1,16 +1,9 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity;
using System;
namespace SourceGit.Views { namespace SourceGit.Views {
public partial class StashesPage : UserControl { public partial class StashesPage : UserControl {
public StashesPage() { public StashesPage() {
InitializeComponent(); InitializeComponent();
} }
protected override void OnUnloaded(RoutedEventArgs e) {
base.OnUnloaded(e);
GC.Collect();
}
} }
} }

View file

@ -2,7 +2,6 @@ using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace SourceGit.Views { namespace SourceGit.Views {
@ -11,18 +10,6 @@ namespace SourceGit.Views {
InitializeComponent(); InitializeComponent();
} }
protected override void OnUnloaded(RoutedEventArgs e) {
var vm = DataContext as ViewModels.WorkingCopy;
vm.SelectedStagedChange = null;
vm.SelectedStagedTreeNode = null;
vm.SelectedUnstagedChange = null;
vm.SelectedUnstagedTreeNode = null;
vm.SetDetail(null, false);
base.OnUnloaded(e);
GC.Collect();
}
private void ViewAssumeUnchanged(object sender, RoutedEventArgs e) { private void ViewAssumeUnchanged(object sender, RoutedEventArgs e) {
var repoPage = this.FindAncestorOfType<Repository>(); var repoPage = this.FindAncestorOfType<Repository>();
if (repoPage != null) { if (repoPage != null) {