Skip to content

Commit 1d70f29

Browse files
committed
Refactor MainViewModel and update MainPage UI
Refactored MainViewModel to improve property change notifications: - Added `[NotifyPropertyChangedFor]` attributes to relevant fields. - Removed redundant `OnPropertyChanged` calls. - Added `IsIndeterminate` property to reflect busy/error states. - Modified exception handling to set `IsError` appropriately. Updated MainPage.xaml to enhance UI bindings - Enabled/disabled buttons based on `IsBusy` state. - Re-enabled `Logout` button. - Bound `ProgressBar` properties to `IsIndeterminate` and `IsError`. - Added converter for DirectoryObjects.Value
1 parent 43bbc7d commit 1d70f29

File tree

9 files changed

+84
-55
lines changed

9 files changed

+84
-55
lines changed

MsGraphSamples.Services/GraphExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ public static async IAsyncEnumerable<TEntity> ToAsyncEnumerable<TEntity, TCollec
6262
}
6363
}
6464

65-
public static List<TEntity> GetValue<TEntity>(this BaseCollectionPaginationCountResponse collectionResponse) where TEntity : Entity
65+
public static List<TEntity> GetValue<TEntity>(this BaseCollectionPaginationCountResponse? collectionResponse) where TEntity : Entity
6666
{
67-
return collectionResponse.BackingStore.Get<List<TEntity>>("value") ?? [];
67+
return collectionResponse?.BackingStore.Get<List<TEntity>>("value") ?? [];
6868
}
6969

7070
public static async Task<TCollectionResponse?> GetNextPageAsync<TCollectionResponse>(this TCollectionResponse? collectionResponse, IRequestAdapter requestAdapter, CancellationToken cancellationToken = default)

MsGraphSamples.WPF/Helpers/Converters.cs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using Microsoft.Graph.Models;
5+
using MsGraphSamples.Services;
56
using System.Globalization;
67
using System.Windows.Data;
78

@@ -19,7 +20,6 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
1920

2021
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
2122
}
22-
2323
public class DirectoryObjectsCountConverter : IValueConverter
2424
{
2525
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
@@ -29,9 +29,31 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
2929
if (directoryObjects == null)
3030
return string.Empty;
3131

32-
var directoryObjectCollection = directoryObjects.BackingStore?.Get<IEnumerable<DirectoryObject>?>("value");
33-
return $"{directoryObjectCollection?.Count() ?? 0} / {directoryObjects.OdataCount}";
32+
var directoryObjectCollection = directoryObjects.BackingStore.Get<IEnumerable<DirectoryObject>>("value") ?? [];
33+
return $"{directoryObjectCollection.Count()} / {directoryObjects.OdataCount}";
3434
}
3535

3636
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
37-
}
37+
}
38+
39+
public class DirectoryObjectsValueConverter : IValueConverter
40+
{
41+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
42+
{
43+
var directoryObjects = (BaseCollectionPaginationCountResponse?)value;
44+
return directoryObjects switch
45+
{
46+
UserCollectionResponse => directoryObjects.GetValue<User>(),
47+
GroupCollectionResponse => directoryObjects.GetValue<Group>(),
48+
ApplicationCollectionResponse => directoryObjects.GetValue<Application>(),
49+
ServicePrincipalCollectionResponse => directoryObjects.GetValue<ServicePrincipal>(),
50+
DeviceCollectionResponse => directoryObjects.GetValue<Device>(),
51+
_ => Enumerable.Empty<DirectoryObject>()
52+
};
53+
}
54+
55+
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
56+
{
57+
throw new NotImplementedException();
58+
}
59+
}

MsGraphSamples.WPF/ViewModels/MainViewModel.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public partial class MainViewModel(IAuthService authService, IGraphDataService g
4040
[ObservableProperty]
4141
[NotifyCanExecuteChangedFor(nameof(LaunchGraphExplorerCommand))]
4242
[NotifyCanExecuteChangedFor(nameof(LoadNextPageCommand))]
43+
[NotifyPropertyChangedFor(nameof(LastUrl))]
4344
private BaseCollectionPaginationCountResponse? _directoryObjects;
4445

4546
#region OData Operators
@@ -164,8 +165,8 @@ private async Task LoadNextPage()
164165
private Task Sort(DataGridSortingEventArgs e)
165166
{
166167
OrderBy = e.Column.SortDirection == null || e.Column.SortDirection == ListSortDirection.Descending
167-
? $"{e.Column.Header} asc"
168-
: $"{e.Column.Header} desc";
168+
? $"{e.Column.Header} asc"
169+
: $"{e.Column.Header} desc";
169170

170171
// Prevent client-side sorting
171172
e.Handled = true;
@@ -230,7 +231,6 @@ private async Task IsBusyWrapper(Func<Task> loadOperation)
230231
{
231232
_stopWatch.Stop();
232233
OnPropertyChanged(nameof(ElapsedMs));
233-
OnPropertyChanged(nameof(LastUrl));
234234
IsBusy = false;
235235
}
236236
}

MsGraphSamples.WPF/ViewModels/ViewModelLocator.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,16 @@ private static ServiceProvider GetServices()
2525
{
2626
var serviceCollection = new ServiceCollection();
2727

28-
if (IsInDesignMode)
29-
{
30-
}
31-
else
28+
if (!IsInDesignMode)
3229
{
3330
var authService = new AuthService();
3431
serviceCollection.AddSingleton<IAuthService>(authService);
3532

3633
var graphDataService = new GraphDataService(authService.GraphClient);
3734
serviceCollection.AddSingleton<IGraphDataService>(graphDataService);
38-
39-
var asyncEnumerableGraphDataService = new AsyncEnumerableGraphDataService(authService.GraphClient);
40-
serviceCollection.AddSingleton<IAsyncEnumerableGraphDataService>(asyncEnumerableGraphDataService);
4135
}
4236

43-
serviceCollection.AddSingleton<MainViewModel>();
37+
serviceCollection.AddTransient<MainViewModel>();
4438

4539
return serviceCollection.BuildServiceProvider();
4640
}

MsGraphSamples.WPF/Views/MainWindow.xaml

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
<Grid>
4949
<Grid.Resources>
5050
<converters:DirectoryObjectsCountConverter x:Key="DirectoryObjectsCount" />
51+
<converters:DirectoryObjectsValueConverter x:Key="DirectoryObjectsValue" />
5152
</Grid.Resources>
5253
<Grid.RowDefinitions>
5354
<RowDefinition Height="Auto" />
@@ -154,12 +155,7 @@
154155
Grid.Row="0"
155156
Grid.Column="2"
156157
Margin="6,0">
157-
<TextBlock FontWeight="Bold">
158-
<Run Text="Entities: "/>
159-
<Run Text="{Binding DirectoryObjects.Value.Count, Mode=OneWay}"/>
160-
<Run Text=" / "/>
161-
<Run Text="{Binding DirectoryObjects.OdataCount, Mode=OneWay}" />
162-
</TextBlock>
158+
<TextBlock FontWeight="Bold" Text="{Binding DirectoryObjects, Mode=OneWay, Converter={StaticResource DirectoryObjectsCount}, StringFormat='Entities: {0}', FallbackValue='Entities:'}" />
163159
<ComboBox
164160
Width="120"
165161
ItemsSource="{Binding Entities}"
@@ -220,7 +216,7 @@
220216
AutoGeneratedColumns="ResultsDataGrid_AutoGeneratedColumns"
221217
AutoGeneratingColumn="ResultsDataGrid_AutoGeneratingColumn"
222218
IsReadOnly="True"
223-
ItemsSource="{Binding DirectoryObjects.Value}"
219+
ItemsSource="{Binding DirectoryObjects, Converter={StaticResource DirectoryObjectsValue}}"
224220
SelectedItem="{Binding SelectedObject}"
225221
SelectionMode="Single">
226222
<i:Interaction.Triggers>

MsGraphSamples.WinUI/App.xaml.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using MsGraphSamples.Services;
77
using MsGraphSamples.WinUI.ViewModels;
88
using MsGraphSamples.WinUI.Views;
9+
using Windows.ApplicationModel;
910

1011
// To learn more about WinUI, the WinUI project structure,
1112
// and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -32,11 +33,14 @@ private static ServiceProvider GetServices()
3233
{
3334
var serviceCollection = new ServiceCollection();
3435

35-
var authService = new AuthService();
36-
serviceCollection.AddSingleton<IAuthService>(authService);
36+
if (!DesignMode.DesignModeEnabled)
37+
{
38+
var authService = new AuthService();
39+
serviceCollection.AddSingleton<IAuthService>(authService);
3740

38-
var asyncEnumerableGraphDataService = new AsyncEnumerableGraphDataService(authService.GraphClient);
39-
serviceCollection.AddSingleton<IAsyncEnumerableGraphDataService>(asyncEnumerableGraphDataService);
41+
var asyncEnumerableGraphDataService = new AsyncEnumerableGraphDataService(authService.GraphClient);
42+
serviceCollection.AddSingleton<IAsyncEnumerableGraphDataService>(asyncEnumerableGraphDataService);
43+
}
4044

4145
serviceCollection.AddTransient<MainViewModel>();
4246

MsGraphSamples.WinUI/MsGraphSamples.WinUI.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
<ItemGroup>
3232
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
33+
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.0.240109" />
3334
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls.DataGrid" Version="7.1.2" />
3435
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
3536
<PackageReference Include="Microsoft.Graph" Version="5.56.0" />

MsGraphSamples.WinUI/ViewModels/MainViewModel.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,22 @@ public partial class MainViewModel(IAuthService authService, IAsyncEnumerableGra
2626
public long ElapsedMs => _stopWatch.ElapsedMilliseconds;
2727

2828
[ObservableProperty]
29+
[NotifyPropertyChangedFor(nameof(IsIndeterminate))]
2930
private bool _isBusy = false;
3031

3132
[ObservableProperty]
33+
[NotifyPropertyChangedFor(nameof(IsIndeterminate))]
3234
private bool _isError = false;
3335

36+
public bool IsIndeterminate => IsBusy || IsError;
37+
3438
[ObservableProperty]
3539
private string? _userName;
3640

3741
[ObservableProperty]
3842
[NotifyCanExecuteChangedFor(nameof(LaunchGraphExplorerCommand))]
43+
[NotifyPropertyChangedFor(nameof(LastUrl))]
44+
[NotifyPropertyChangedFor(nameof(LastCount))]
3945
private AsyncLoadingCollection<DirectoryObject>? _directoryObjects;
4046

4147
[ObservableProperty]
@@ -204,7 +210,6 @@ private async Task IsBusyWrapper(IAsyncEnumerable<DirectoryObject> directoryObje
204210
DirectoryObjects = new(directoryObjects, pageSize);
205211
await DirectoryObjects.LoadMoreItemsAsync();
206212

207-
208213
SelectedEntity = DirectoryObjects.FirstOrDefault() switch
209214
{
210215
User => "Users",
@@ -223,14 +228,12 @@ private async Task IsBusyWrapper(IAsyncEnumerable<DirectoryObject> directoryObje
223228
catch (ApiException ex)
224229
{
225230
IsError = true;
226-
await ShowDialogAsync(ex.Message, Enum.GetName((HttpStatusCode)ex.ResponseStatusCode));
231+
await ShowDialogAsync(Enum.GetName((HttpStatusCode)ex.ResponseStatusCode)!, ex.Message);
227232
}
228233
finally
229234
{
230235
_stopWatch.Stop();
231236
OnPropertyChanged(nameof(ElapsedMs));
232-
OnPropertyChanged(nameof(LastUrl));
233-
OnPropertyChanged(nameof(LastCount));
234237
IsBusy = false;
235238
}
236239
}

MsGraphSamples.WinUI/Views/MainPage.xaml

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,22 @@
55
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
66
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
77
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
8+
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
89
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
910
xmlns:graph="using:Microsoft.Graph.Models"
1011
xmlns:i="using:Microsoft.Xaml.Interactivity"
1112
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
12-
xmlns:sys="using:System" Loaded="Page_Loaded"
1313
xmlns:vm="using:MsGraphSamples.WinUI.ViewModels"
1414
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
15+
Loaded="Page_Loaded"
1516
mc:Ignorable="d">
1617

17-
<Grid Margin="0,24,0,0">
18+
<Page.Resources>
19+
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
20+
<converters:StringFormatConverter x:Key="StringFormatConverter" />
21+
</Page.Resources>
22+
23+
<Grid Margin="0,28,0,0">
1824
<Grid.RowDefinitions>
1925
<RowDefinition Height="Auto" />
2026
<RowDefinition Height="*" />
@@ -84,10 +90,13 @@
8490
Margin="6,0"
8591
VerticalAlignment="Bottom">
8692

87-
<TextBlock
88-
Margin="0,3"
89-
FontWeight="Bold"
90-
Text="{x:Bind sys:String.Format(x:Null, 'Entities: {0:N0} / {1:N0}', ViewModel.DirectoryObjects.Count, ViewModel.LastCount), Mode=OneWay}" />
93+
<TextBlock Margin="0,3" FontWeight="Bold">
94+
<Run Text="Entities: " />
95+
<Run Text="{x:Bind ViewModel.DirectoryObjects.Count, Mode=OneWay}" />
96+
<Run Text=" / " />
97+
<Run Text="{x:Bind ViewModel.LastCount, Mode=OneWay}" />
98+
</TextBlock>
99+
91100
<ComboBox
92101
HorizontalAlignment="Stretch"
93102
ItemsSource="{x:Bind vm:MainViewModel.Entities}"
@@ -147,7 +156,8 @@
147156
HorizontalAlignment="Stretch"
148157
VerticalAlignment="Bottom"
149158
Command="{x:Bind ViewModel.LoadCommand}"
150-
Content="Load" />
159+
Content="Load"
160+
IsEnabled="{x:Bind ViewModel.IsBusy, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}" />
151161

152162
<!-- Graph Explorer -->
153163
<TextBox
@@ -165,7 +175,8 @@
165175
HorizontalAlignment="Stretch"
166176
VerticalAlignment="Stretch"
167177
Command="{x:Bind ViewModel.LaunchGraphExplorerCommand}"
168-
Content="Graph Explorer" />
178+
Content="Graph Explorer"
179+
IsEnabled="{x:Bind ViewModel.IsBusy, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}" />
169180
</Grid>
170181

171182
<!-- Query Results -->
@@ -195,10 +206,7 @@
195206
</i:Interaction.Behaviors>
196207
</controls:DataGrid>
197208

198-
<Grid
199-
Grid.Row="2"
200-
Margin="6"
201-
Background="{ThemeResource AcrylicBackgroundFillColorBaseBrush}">
209+
<Grid Grid.Row="2" Margin="6">
202210
<Grid.ColumnDefinitions>
203211
<ColumnDefinition Width="Auto" />
204212
<ColumnDefinition Width="Auto" />
@@ -209,25 +217,26 @@
209217
<TextBlock
210218
Margin="6,0"
211219
VerticalAlignment="Center"
212-
Text="{x:Bind sys:String.Format('Hello {0}', ViewModel.UserName), Mode=OneWay}" />
220+
Text="{x:Bind ViewModel.UserName, Mode=OneWay, Converter={StaticResource StringFormatConverter}, ConverterParameter='Hello {0}'}" />
213221

214-
<!--<Button Grid.Column="2"
215-
Margin="0,0,6,0"
216-
Padding="6,0"
217-
Command="{x:Bind ViewModel.LogoutCommand}"
218-
Content="Logout" />-->
222+
<Button
223+
Grid.Column="1"
224+
Margin="6,0"
225+
Padding="6,0"
226+
Command="{x:Bind ViewModel.LogoutCommand}"
227+
Content="Logout" />
219228

220229
<ProgressBar
221230
Grid.Column="2"
222-
Margin="0,0,6,0"
223-
HorizontalContentAlignment="Stretch"
224-
IsIndeterminate="{x:Bind ViewModel.IsBusy, Mode=OneWay}"
231+
Margin="6,0"
232+
VerticalAlignment="Center"
233+
IsIndeterminate="{x:Bind ViewModel.IsIndeterminate, Mode=OneWay}"
225234
ShowError="{x:Bind ViewModel.IsError, Mode=OneWay}" />
226235

227236
<TextBlock
228237
Grid.Column="3"
229-
Margin="0,0,6,0"
230-
Text="{x:Bind sys:String.Format('{0:N0} ms', ViewModel.ElapsedMs), Mode=OneWay}" />
238+
Margin="6,0"
239+
Text="{x:Bind ViewModel.ElapsedMs, Mode=OneWay, Converter={StaticResource StringFormatConverter}, ConverterParameter='{}{0:N0} ms'}" />
231240
</Grid>
232241
</Grid>
233242
</Page>

0 commit comments

Comments
 (0)