Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit 0ccf448

Browse files
committed
Add a way to bind ICommands to VS commands.
And use it in the `GitHubPaneViewModel`.
1 parent 98557f0 commit 0ccf448

File tree

3 files changed

+106
-23
lines changed

3 files changed

+106
-23
lines changed

src/GitHub.App/GitHub.App.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,10 @@
311311
<Project>{8d73575a-a89f-47cc-b153-b47dd06837f0}</Project>
312312
<Name>GitHub.Logging</Name>
313313
</ProjectReference>
314+
<ProjectReference Include="..\GitHub.Services.Vssdk\GitHub.Services.Vssdk.csproj">
315+
<Project>{2D3D2834-33BE-45CA-B3CC-12F853557D7B}</Project>
316+
<Name>GitHub.Services.Vssdk</Name>
317+
</ProjectReference>
314318
<ProjectReference Include="..\GitHub.UI.Reactive\GitHub.UI.Reactive.csproj">
315319
<Project>{158b05e8-fdbc-4d71-b871-c96e28d5adf5}</Project>
316320
<Name>GitHub.UI.Reactive</Name>

src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.ComponentModel.Composition;
3+
using System.ComponentModel.Design;
34
using System.Linq;
45
using System.Reactive;
56
using System.Reactive.Linq;
@@ -14,6 +15,7 @@
1415
using GitHub.Models;
1516
using GitHub.Primitives;
1617
using GitHub.Services;
18+
using GitHub.Services.Vssdk.Commands;
1719
using GitHub.VisualStudio;
1820
using ReactiveUI;
1921
using OleMenuCommand = Microsoft.VisualStudio.Shell.OleMenuCommand;
@@ -43,6 +45,8 @@ public sealed class GitHubPaneViewModel : ViewModelBase, IGitHubPaneViewModel, I
4345
readonly ObservableAsPropertyHelper<ContentOverride> contentOverride;
4446
readonly ObservableAsPropertyHelper<bool> isSearchEnabled;
4547
readonly ObservableAsPropertyHelper<string> title;
48+
readonly ReactiveCommand<object> navigateBack;
49+
readonly ReactiveCommand<object> navigateForward;
4650
readonly ReactiveCommand<Unit> refresh;
4751
readonly ReactiveCommand<Unit> showPullRequests;
4852
readonly ReactiveCommand<object> openInBrowser;
@@ -125,6 +129,14 @@ public GitHubPaneViewModel(
125129
.Select(x => x is ISearchablePageViewModel)
126130
.ToProperty(this, x => x.IsSearchEnabled);
127131

132+
navigateBack = ReactiveCommand.CreateCombined(
133+
currentPage.Select(x => x != null),
134+
navigator.NavigateBack);
135+
136+
navigateForward = ReactiveCommand.CreateCombined(
137+
currentPage.Select(x => x != null),
138+
navigator.NavigateForward);
139+
128140
refresh = ReactiveCommand.CreateAsyncTask(
129141
currentPage.SelectMany(x => x?.WhenAnyValue(
130142
y => y.IsLoading,
@@ -213,11 +225,12 @@ public async Task InitializeAsync(IServiceProvider paneServiceProvider)
213225

214226
connectionManager.Connections.CollectionChanged += (_, __) => UpdateContent(LocalRepository).Forget();
215227

216-
BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.pullRequestCommand, showPullRequests);
217-
BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.backCommand, navigator.NavigateBack);
218-
BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.forwardCommand, navigator.NavigateForward);
219-
BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.refreshCommand, refresh);
220-
BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.githubCommand, openInBrowser);
228+
var menuService = (IMenuCommandService)paneServiceProvider.GetService(typeof(IMenuCommandService));
229+
BindNavigatorCommand(menuService, PkgCmdIDList.pullRequestCommand, showPullRequests);
230+
BindNavigatorCommand(menuService, PkgCmdIDList.backCommand, navigator.NavigateBack);
231+
BindNavigatorCommand(menuService, PkgCmdIDList.forwardCommand, navigator.NavigateForward);
232+
BindNavigatorCommand(menuService, PkgCmdIDList.refreshCommand, refresh);
233+
BindNavigatorCommand(menuService, PkgCmdIDList.githubCommand, openInBrowser);
221234

222235
paneServiceProvider.AddCommandHandler(Guids.guidGitHubToolbarCmdSet, PkgCmdIDList.helpCommand,
223236
(_, __) =>
@@ -304,27 +317,12 @@ public Task ShowPullRequest(string owner, string repo, int number)
304317
x => x.RemoteRepositoryOwner == owner && x.LocalRepository.Name == repo && x.Number == number);
305318
}
306319

307-
OleMenuCommand BindNavigatorCommand<T>(IServiceProvider paneServiceProvider, int commandId, ReactiveCommand<T> command)
320+
OleMenuCommand BindNavigatorCommand<T>(IMenuCommandService menu, int commandId, ReactiveCommand<T> command)
308321
{
309-
Guard.ArgumentNotNull(paneServiceProvider, nameof(paneServiceProvider));
322+
Guard.ArgumentNotNull(menu, nameof(menu));
310323
Guard.ArgumentNotNull(command, nameof(command));
311324

312-
Func<bool> canExecute = () => Content == navigator && command.CanExecute(null);
313-
314-
var result = paneServiceProvider.AddCommandHandler(
315-
Guids.guidGitHubToolbarCmdSet,
316-
commandId,
317-
canExecute,
318-
() => command.Execute(null),
319-
true);
320-
321-
Observable.CombineLatest(
322-
this.WhenAnyValue(x => x.Content),
323-
command.CanExecuteObservable,
324-
(c, e) => c == navigator && e)
325-
.Subscribe(x => result.Enabled = x);
326-
327-
return result;
325+
return menu.BindCommand(new CommandID(Guids.guidGitHubToolbarCmdSet, commandId), command);
328326
}
329327

330328
async Task NavigateTo<TViewModel>(Func<TViewModel, Task> initialize, Func<TViewModel, bool> match = null)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,95 @@
11
using System;
22
using System.ComponentModel.Design;
3+
using System.Windows.Input;
34
using GitHub.Commands;
5+
using GitHub.Extensions;
6+
using Microsoft.VisualStudio.Shell;
47

58
namespace GitHub.Services.Vssdk.Commands
69
{
10+
/// <summary>
11+
/// Extension methods for <see cref="IMenuCommandService"/>.
12+
/// </summary>
713
public static class MenuCommandServiceExtensions
814
{
15+
/// <summary>
16+
/// Adds an <see cref="IVsCommand"/> or <see cref="IVsCommand{TParam}"/> to a menu.
17+
/// </summary>
18+
/// <param name="service">The menu command service.</param>
19+
/// <param name="command">The command to add.</param>
920
public static void AddCommand(this IMenuCommandService service, IVsCommandBase command)
1021
{
22+
Guard.ArgumentNotNull(service, nameof(service));
23+
Guard.ArgumentNotNull(command, nameof(command));
24+
1125
service.AddCommand((MenuCommand)command);
1226
}
27+
28+
/// <summary>
29+
/// Binds an <see cref="ICommand"/> to a Visual Studio command.
30+
/// </summary>
31+
/// <param name="service">The menu command service.</param>
32+
/// <param name="id">The ID of the visual studio command.</param>
33+
/// <param name="command">The <see cref="ICommand"/> to bind</param>
34+
/// <param name="hideWhenDisabled">
35+
/// If true, the visual studio command will be hidden when disabled.
36+
/// </param>
37+
/// <remarks>
38+
/// This method wires up the <paramref name="command"/> to be executed when the Visual Studio
39+
/// command is invoked, and for the <paramref name="command"/>'s
40+
/// <see cref="ICommand.CanExecute(object)"/> state to control the enabled/visible state of
41+
/// the Visual Studio command.
42+
/// </remarks>
43+
/// <returns>
44+
/// The created <see cref="OleMenuCommand"/>.
45+
/// </returns>
46+
public static OleMenuCommand BindCommand(
47+
this IMenuCommandService service,
48+
CommandID id,
49+
ICommand command,
50+
bool hideWhenDisabled = false)
51+
{
52+
Guard.ArgumentNotNull(service, nameof(service));
53+
Guard.ArgumentNotNull(id, nameof(id));
54+
Guard.ArgumentNotNull(command, nameof(command));
55+
56+
var bound = new BoundCommand(id, command, hideWhenDisabled);
57+
service.AddCommand(bound);
58+
return bound;
59+
}
60+
61+
class BoundCommand : OleMenuCommand
62+
{
63+
readonly ICommand inner;
64+
readonly bool hideWhenDisabled;
65+
66+
public BoundCommand(CommandID id, ICommand command, bool hideWhenDisabled)
67+
: base(InvokeHandler, delegate { }, HandleBeforeQueryStatus, id)
68+
{
69+
Guard.ArgumentNotNull(id, nameof(id));
70+
Guard.ArgumentNotNull(command, nameof(command));
71+
72+
inner = command;
73+
this.hideWhenDisabled = hideWhenDisabled;
74+
inner.CanExecuteChanged += (s, e) => HandleBeforeQueryStatus(this, e);
75+
}
76+
77+
static void InvokeHandler(object sender, EventArgs e)
78+
{
79+
var command = sender as BoundCommand;
80+
command?.inner.Execute((e as OleMenuCmdEventArgs)?.InValue);
81+
}
82+
83+
static void HandleBeforeQueryStatus(object sender, EventArgs e)
84+
{
85+
var command = sender as BoundCommand;
86+
87+
if (command != null)
88+
{
89+
command.Enabled = command.inner.CanExecute(null);
90+
command.Visible = command.hideWhenDisabled ? command.Enabled : true;
91+
}
92+
}
93+
}
1394
}
1495
}

0 commit comments

Comments
 (0)