-
Notifications
You must be signed in to change notification settings - Fork 1.2k
MVVM-ize things #1346
MVVM-ize things #1346
Conversation
53bb5e6
to
5106ff5
Compare
: base(exception.Message, innerException: exception) | ||
{ | ||
Guard.ArgumentNotNull(exception, nameof(exception)); | ||
|
||
TwoFactorType = exception.TwoFactorType; | ||
TwoFactorType = twoFactorType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added this extra constructor because the detection of the two-factor type is deep within Octokit's header parsing code. This allows us to easily override that in unit tests.
</DataTemplate> | ||
</ContentControl.Resources> | ||
</ContentControl> | ||
<TextBlock Grid.Column="1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to make this change because for some reason the old way of doing it was causing a StackOverflowException
in WPF's layouting code. This change should produce equivalent results and is in fact simpler.
...instead of `IGitHubServiceProvider` to create view models in `GitHubDialogWindowViewModel`. Also added some doc comments.
And use it instead of defining `LoginView` and `NavigationView` separately.
MEF requires an array here.
When the page to be navigated to in the GitHub pane is the current page, do nothing.
ICompositionService cc) | ||
{ | ||
this.serviceProvider = serviceProvider; | ||
cc.SatisfyImportsOnce(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think importing ICompositionService
or calling SatisfyImportsOnce
is necessary. This should happen anyway after ViewViewModelFactory
has been created.
I'm not sure AllowRecomposition = true
is supported in Visual Studio's MEF implementation. I'm guessing this class some from somewhere else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure - this class used to be ExportFactoryProvider
and the code comes from there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this query worth putting in another issue under "needs investigation" so that we can look into it after this PR gets merged?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put a breakpoint on the set
method of Views
and this property is indeed being set twice (with the same number of elements).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
</DataTemplate> | ||
</Window.Resources> | ||
</pfui:DialogWindow> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be </Window>
?
|
||
<!-- Display ViewModel.Greeting in a red border with rounded corners --> | ||
<Border Background="Red" CornerRadius="8"> | ||
<TextBlock Binding="{Binding Greeting}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing a />
.
This commit will automatically dispose of any preview PullRequestDetailViewModel object.
Keep CA happy by making AutoDispose static.
PullRequestDetailViewModel will be disposed in future commit.
Lightweight fix for PR detail view not refreshing
Conflicts: src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good!
See comment about the CA1004
warning.
`IVSGitExt.ActiveRepositoriesChanged` isn't notified on the UI thread which breaks things. Switch to the main thread when refreshing due to a notification from this.
|
||
And thus a view model becomes a view. | ||
|
||
[1]: it would be nice to make it apply to all classes that inherit `IViewModel` but unfortunately WPF's `DataTemplate`s don't work with interfaces. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sure I'm missing something, but is this not a DataTemplate
that uses an interface?
https://stackoverflow.com/a/25642988/121348
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, interesting, I didn't know this was possible! Worth a try!
cc.SatisfyImportsOnce(this); | ||
} | ||
|
||
[ImportMany(AllowRecomposition = true)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Visual Studio's MEF implementation is static (the graph is created once and cached). New exports/assemblies can't be added to the catalog on the fly. Having AllowRecomposition = true
is confusing because recomposition is impossible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, well I'm fine with removing it! We should probably do it in a new PR though in case it causes problems.
/// <inheritdoc/> | ||
public TViewModel CreateViewModel<TViewModel>() where TViewModel : IViewModel | ||
{ | ||
return serviceProvider.ExportProvider.GetExport<TViewModel>().Value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could do the same as with CreateView
and avoid any dependency on IGitHubServiceProvider
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep! Lets try that in a separate PR too.
ICompositionService cc) | ||
{ | ||
this.serviceProvider = serviceProvider; | ||
cc.SatisfyImportsOnce(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this query worth putting in another issue under "needs investigation" so that we can look into it after this PR gets merged?
@@ -131,19 +132,40 @@ | |||
<Reference Include="WindowsBase" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Compile Include="Factories\ViewViewModelFactory.cs" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm assuming "ViewViewModelFactory.cs" is meant to have two "view"'s and isn't a typo. But thought it was worth asking ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's used for creating both views and view models. I did think it looked strange until I thought that MVVM stands for "model, view, view model" and so I decided to keep it.
{ | ||
history = new ReactiveList<IPanePageViewModel>(); | ||
history.BeforeItemsAdded.Subscribe(BeforeItemAdded); | ||
history.CollectionChanged += CollectionChanged; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where are BeforeItemAdded and CollectionChanged coming from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NVM - I see them below.
This pull request migrates our code to use the MVVM pattern throughout. MVVM is generally considered to be the "right" way to do WPF and greatly improves the unit-testability of code by separating application layer and view layer code.
This is a big PR, sorry! To ease reviewing, the following attempts to explain the major changes and concepts.
UIController Removed
UIController
was previously responsible for creating views and view models based on theUIViewType
enumeration.UIController
after the refactor; it's view models all the way down!UIViewType
, view models and views are keyed on the type of the view model interfaceNavigationController Removed
NavigationController
was built onUIController
to display the browser-style user interface in the GitHub pane. The equivalent now isNavigationViewModel
, however this simply maintains a list of pages and a position in that list, it knows nothing about the pages it will display. That responsibility is handled by:GitHubPaneViewModel
GitHubPaneViewModel
exposes aContent
property which:NavigationViewModel
Content
will be a view model representing this state such as anILoggedOutViewModel
GitHubPaneViewModel
supports navigation via URLs, such asgithub://pane/owner/repo/pull/5
(viaNavigateTo(Uri)
) or by calling a method such asShowPullRequest("owner", "repo", 5)
.Asynchronous Initialization
Previously view model initialization methods were all of the signature
void Initialize(ViewWithData)
(this was defined in theIViewModel
base interface). This had three downsides:object
and placed inViewWithData.Data
The base
IViewModel
interface no longer defines a one-fits-all initialization method and view models are now expected to define a (probablyasync
) initialization method in their interface which must be called following creation.Many of the changes in this PR consist of moving things from the ViewModel constructor to an async initialization method.
ViewLocator
At the root of each GitHub top-level control (i.e. dialog and the GitHub pane) there is a
DataTemplate
typed onViewModelBase
. ThisDataTemplate
has aViewLocator
as its converter and is used to convert view models to their equivalent views. Using this technique the application layer code need have no concept of the view layer: the application state is built purely with view models which are converted to equivalent views by the view layer.See the documentation here.
Views and View Models Moved
Previously all views and view models were all in the same folder/namespace, which was starting to get unwieldy. I've moved them under folders based on which part of the UI they appear in:
Dialog
,GitHubPane
andTeamExplorer
.Developer Documentation Added
I've added a few pieces of developer documentation explaining some of the new features:
https://github.com/github/VisualStudio/blob/refactor/mvvm/docs/developer/readme.md
Fixes #1105
Fixes #1341
Fixes #1357