mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-15 19:27:56 +01:00
CmdPal: Fix grid views (#43991)
## Summary of the Pull Request
This PR fixes the crash due to binding to a trimmed property. For this
it converts runtime bindings on GridView to use `{x:Bind}` so this issue
can't happen in the future.
- Fixes a crash related to the `Visibility` property in gallery/grid
views when trimmed during AOT builds.
- Fixes ShowTitle and ShowSubtitle properties, they are now taken into
account in a view.
- Improves UI layout, removes some margins and maches the corner radius
of the item contaienr with the item content in the gallery view.
- Refactores gallery and grid views to move logic from the view to the
view model so we can x:Bind to them.
- Replaces `{Binding}` with `{x:Bind}` to improve performance and enable
compile-time binding validation.
- Properties related to grids are splatted on to the common
`IGridPropertiesViewModel` interface. Subclassing would add extra
overhead without substential benefit.
- Adds new samples to showcase various grid view configurations.
## Pictures? Pictures!
A) Gallery view (with title and subtitle)
<img width="909" height="583" alt="image"
src="https://github.com/user-attachments/assets/b807e7a8-412f-4817-8121-e3470c49e0c0"
/>
B) Gallery view (only title)
<img width="903" height="582" alt="image"
src="https://github.com/user-attachments/assets/b619d63f-04d0-42f2-9207-de256dc5e481"
/>
C) Gallery view (no title or subtitle)
<img width="900" height="583" alt="image"
src="https://github.com/user-attachments/assets/c48cd1fc-8f51-40c1-8bce-607916e9f742"
/>
D) Small icons
<img width="907" height="582" alt="image"
src="https://github.com/user-attachments/assets/8327da0a-fa45-443f-b52c-f0f1edd7b861"
/>
E) Medium icons (with labels)
<img width="914" height="588" alt="image"
src="https://github.com/user-attachments/assets/dee9fab1-54e8-45f8-96d7-502b121a6ac2"
/>
F) Medium icons (no labels)
<img width="915" height="588" alt="image"
src="https://github.com/user-attachments/assets/a32e8af2-6cb1-4106-91db-ca396253c0a3"
/>
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #43973
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
This commit is contained in:
@@ -11,15 +11,15 @@ public class GalleryGridPropertiesViewModel : IGridPropertiesViewModel
|
||||
{
|
||||
private readonly ExtensionObject<IGalleryGridLayout> _model;
|
||||
|
||||
public bool ShowTitle { get; private set; }
|
||||
|
||||
public bool ShowSubtitle { get; private set; }
|
||||
|
||||
public GalleryGridPropertiesViewModel(IGalleryGridLayout galleryGridLayout)
|
||||
{
|
||||
_model = new(galleryGridLayout);
|
||||
}
|
||||
|
||||
public bool ShowTitle { get; set; }
|
||||
|
||||
public bool ShowSubtitle { get; set; }
|
||||
|
||||
public void InitializeProperties()
|
||||
{
|
||||
var model = _model.Unsafe;
|
||||
|
||||
@@ -6,5 +6,9 @@ namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public interface IGridPropertiesViewModel
|
||||
{
|
||||
bool ShowTitle { get; }
|
||||
|
||||
bool ShowSubtitle { get; }
|
||||
|
||||
void InitializeProperties();
|
||||
}
|
||||
|
||||
@@ -10,10 +10,9 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public partial class ListItemViewModel(IListItem model, WeakReference<IPageContext> context)
|
||||
: CommandItemViewModel(new(model), context)
|
||||
public partial class ListItemViewModel : CommandItemViewModel
|
||||
{
|
||||
public new ExtensionObject<IListItem> Model { get; } = new(model);
|
||||
public new ExtensionObject<IListItem> Model { get; }
|
||||
|
||||
public List<TagViewModel>? Tags { get; set; }
|
||||
|
||||
@@ -32,6 +31,40 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
|
||||
|
||||
public string AccessibleName { get; private set; } = string.Empty;
|
||||
|
||||
public bool ShowTitle { get; private set; }
|
||||
|
||||
public bool ShowSubtitle { get; private set; }
|
||||
|
||||
public bool LayoutShowsTitle
|
||||
{
|
||||
get;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref field, value))
|
||||
{
|
||||
UpdateShowsTitle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool LayoutShowsSubtitle
|
||||
{
|
||||
get;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref field, value))
|
||||
{
|
||||
UpdateShowsSubtitle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ListItemViewModel(IListItem model, WeakReference<IPageContext> context)
|
||||
: base(new(model), context)
|
||||
{
|
||||
Model = new ExtensionObject<IListItem>(model);
|
||||
}
|
||||
|
||||
public override void InitializeProperties()
|
||||
{
|
||||
if (IsInitialized)
|
||||
@@ -93,16 +126,18 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
|
||||
|
||||
switch (propertyName)
|
||||
{
|
||||
case nameof(Tags):
|
||||
case nameof(model.Tags):
|
||||
UpdateTags(model.Tags);
|
||||
break;
|
||||
case nameof(TextToSuggest):
|
||||
this.TextToSuggest = model.TextToSuggest ?? string.Empty;
|
||||
case nameof(model.TextToSuggest):
|
||||
TextToSuggest = model.TextToSuggest ?? string.Empty;
|
||||
UpdateProperty(nameof(TextToSuggest));
|
||||
break;
|
||||
case nameof(Section):
|
||||
this.Section = model.Section ?? string.Empty;
|
||||
case nameof(model.Section):
|
||||
Section = model.Section ?? string.Empty;
|
||||
UpdateProperty(nameof(Section));
|
||||
break;
|
||||
case nameof(Details):
|
||||
case nameof(model.Details):
|
||||
var extensionDetails = model.Details;
|
||||
Details = extensionDetails is not null ? new(extensionDetails, PageContext) : null;
|
||||
Details?.InitializeProperties();
|
||||
@@ -110,16 +145,24 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
|
||||
UpdateProperty(nameof(HasDetails));
|
||||
UpdateShowDetailsCommand();
|
||||
break;
|
||||
case nameof(MoreCommands):
|
||||
case nameof(model.MoreCommands):
|
||||
UpdateProperty(nameof(MoreCommands));
|
||||
AddShowDetailsCommands();
|
||||
break;
|
||||
case nameof(Title):
|
||||
case nameof(Subtitle):
|
||||
case nameof(model.Title):
|
||||
UpdateProperty(nameof(Title));
|
||||
UpdateShowsTitle();
|
||||
UpdateAccessibleName();
|
||||
break;
|
||||
case nameof(model.Subtitle):
|
||||
UpdateProperty(nameof(Subtitle));
|
||||
UpdateShowsSubtitle();
|
||||
UpdateAccessibleName();
|
||||
break;
|
||||
default:
|
||||
UpdateProperty(propertyName);
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateProperty(propertyName);
|
||||
}
|
||||
|
||||
// TODO: Do we want filters to match descriptions and other properties? Tags, etc... Yes?
|
||||
@@ -206,11 +249,32 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
|
||||
// many COM exception issues.
|
||||
Tags = [.. newTags];
|
||||
|
||||
UpdateProperty(nameof(Tags));
|
||||
UpdateProperty(nameof(HasTags));
|
||||
// We're already in UI thread, so just raise the events
|
||||
OnPropertyChanged(nameof(Tags));
|
||||
OnPropertyChanged(nameof(HasTags));
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateShowsTitle()
|
||||
{
|
||||
var oldShowTitle = ShowTitle;
|
||||
ShowTitle = LayoutShowsTitle;
|
||||
if (oldShowTitle != ShowTitle)
|
||||
{
|
||||
UpdateProperty(nameof(ShowTitle));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateShowsSubtitle()
|
||||
{
|
||||
var oldShowSubtitle = ShowSubtitle;
|
||||
ShowSubtitle = LayoutShowsSubtitle && !string.IsNullOrWhiteSpace(Subtitle);
|
||||
if (oldShowSubtitle != ShowSubtitle)
|
||||
{
|
||||
UpdateProperty(nameof(ShowSubtitle));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UnsafeCleanup()
|
||||
{
|
||||
base.UnsafeCleanup();
|
||||
|
||||
@@ -24,8 +24,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
|
||||
// Observable from MVVM Toolkit will auto create public properties that use INotifyPropertyChange change
|
||||
// https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/observablegroupedcollections for grouping support
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<ListItemViewModel> FilteredItems { get; set; } = [];
|
||||
public ObservableCollection<ListItemViewModel> FilteredItems { get; } = [];
|
||||
|
||||
public FiltersViewModel? Filters { get; set; }
|
||||
|
||||
@@ -224,6 +223,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
// TODO we can probably further optimize this by also keeping a
|
||||
// HashSet of every ExtensionObject we currently have, and only
|
||||
// building new viewmodels for the ones we haven't already built.
|
||||
var showsTitle = GridProperties?.ShowTitle ?? true;
|
||||
var showsSubtitle = GridProperties?.ShowSubtitle ?? true;
|
||||
foreach (var item in newItems)
|
||||
{
|
||||
// Check for cancellation during item processing
|
||||
@@ -237,6 +238,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
// If an item fails to load, silently ignore it.
|
||||
if (viewModel.SafeFastInit())
|
||||
{
|
||||
viewModel.LayoutShowsTitle = showsTitle;
|
||||
viewModel.LayoutShowsSubtitle = showsSubtitle;
|
||||
newViewModels.Add(viewModel);
|
||||
}
|
||||
}
|
||||
@@ -583,6 +586,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
GridProperties = LoadGridPropertiesViewModel(model.GridProperties);
|
||||
GridProperties?.InitializeProperties();
|
||||
UpdateProperty(nameof(GridProperties));
|
||||
ApplyLayoutToItems();
|
||||
|
||||
ShowDetails = model.ShowDetails;
|
||||
UpdateProperty(nameof(ShowDetails));
|
||||
@@ -608,22 +612,15 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
model.ItemsChanged += Model_ItemsChanged;
|
||||
}
|
||||
|
||||
private IGridPropertiesViewModel? LoadGridPropertiesViewModel(IGridProperties? gridProperties)
|
||||
private static IGridPropertiesViewModel? LoadGridPropertiesViewModel(IGridProperties? gridProperties)
|
||||
{
|
||||
if (gridProperties is IMediumGridLayout mediumGridLayout)
|
||||
return gridProperties switch
|
||||
{
|
||||
return new MediumGridPropertiesViewModel(mediumGridLayout);
|
||||
}
|
||||
else if (gridProperties is IGalleryGridLayout galleryGridLayout)
|
||||
{
|
||||
return new GalleryGridPropertiesViewModel(galleryGridLayout);
|
||||
}
|
||||
else if (gridProperties is ISmallGridLayout smallGridLayout)
|
||||
{
|
||||
return new SmallGridPropertiesViewModel(smallGridLayout);
|
||||
}
|
||||
|
||||
return null;
|
||||
IMediumGridLayout mediumGridLayout => new MediumGridPropertiesViewModel(mediumGridLayout),
|
||||
IGalleryGridLayout galleryGridLayout => new GalleryGridPropertiesViewModel(galleryGridLayout),
|
||||
ISmallGridLayout smallGridLayout => new SmallGridPropertiesViewModel(smallGridLayout),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
public void LoadMoreIfNeeded()
|
||||
@@ -685,6 +682,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
GridProperties = LoadGridPropertiesViewModel(model.GridProperties);
|
||||
GridProperties?.InitializeProperties();
|
||||
UpdateProperty(nameof(IsGridView));
|
||||
ApplyLayoutToItems();
|
||||
break;
|
||||
case nameof(ShowDetails):
|
||||
ShowDetails = model.ShowDetails;
|
||||
@@ -730,6 +728,21 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
});
|
||||
}
|
||||
|
||||
private void ApplyLayoutToItems()
|
||||
{
|
||||
lock (_listLock)
|
||||
{
|
||||
var showsTitle = GridProperties?.ShowTitle ?? true;
|
||||
var showsSubtitle = GridProperties?.ShowSubtitle ?? true;
|
||||
|
||||
foreach (var item in Items)
|
||||
{
|
||||
item.LayoutShowsTitle = showsTitle;
|
||||
item.LayoutShowsSubtitle = showsSubtitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
@@ -11,13 +11,15 @@ public class MediumGridPropertiesViewModel : IGridPropertiesViewModel
|
||||
{
|
||||
private readonly ExtensionObject<IMediumGridLayout> _model;
|
||||
|
||||
public bool ShowTitle { get; private set; }
|
||||
|
||||
public bool ShowSubtitle => false;
|
||||
|
||||
public MediumGridPropertiesViewModel(IMediumGridLayout mediumGridLayout)
|
||||
{
|
||||
_model = new(mediumGridLayout);
|
||||
}
|
||||
|
||||
public bool ShowTitle { get; set; }
|
||||
|
||||
public void InitializeProperties()
|
||||
{
|
||||
var model = _model.Unsafe;
|
||||
|
||||
@@ -11,6 +11,10 @@ public class SmallGridPropertiesViewModel : IGridPropertiesViewModel
|
||||
{
|
||||
private readonly ExtensionObject<ISmallGridLayout> _model;
|
||||
|
||||
public bool ShowTitle => false;
|
||||
|
||||
public bool ShowSubtitle => false;
|
||||
|
||||
public SmallGridPropertiesViewModel(ISmallGridLayout smallGridLayout)
|
||||
{
|
||||
_model = new(smallGridLayout);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Core.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.CmdPal.UI;
|
||||
|
||||
internal sealed partial class GridItemContainerStyleSelector : StyleSelector
|
||||
{
|
||||
public IGridPropertiesViewModel? GridProperties { get; set; }
|
||||
|
||||
public Style? Small { get; set; }
|
||||
|
||||
public Style? Medium { get; set; }
|
||||
|
||||
public Style? Gallery { get; set; }
|
||||
|
||||
protected override Style? SelectStyleCore(object item, DependencyObject container)
|
||||
{
|
||||
return GridProperties switch
|
||||
{
|
||||
SmallGridPropertiesViewModel => Small,
|
||||
MediumGridPropertiesViewModel => Medium,
|
||||
GalleryGridPropertiesViewModel => Gallery,
|
||||
_ => Medium,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -20,21 +20,12 @@ internal sealed partial class GridItemTemplateSelector : DataTemplateSelector
|
||||
|
||||
protected override DataTemplate? SelectTemplateCore(object item, DependencyObject dependencyObject)
|
||||
{
|
||||
DataTemplate? dataTemplate = Medium;
|
||||
|
||||
if (GridProperties is SmallGridPropertiesViewModel)
|
||||
return GridProperties switch
|
||||
{
|
||||
dataTemplate = Small;
|
||||
}
|
||||
else if (GridProperties is MediumGridPropertiesViewModel)
|
||||
{
|
||||
dataTemplate = Medium;
|
||||
}
|
||||
else if (GridProperties is GalleryGridPropertiesViewModel)
|
||||
{
|
||||
dataTemplate = Gallery;
|
||||
}
|
||||
|
||||
return dataTemplate;
|
||||
SmallGridPropertiesViewModel => Small,
|
||||
MediumGridPropertiesViewModel => Medium,
|
||||
GalleryGridPropertiesViewModel => Gallery,
|
||||
_ => Medium,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,33 +5,151 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cmdpalUI="using:Microsoft.CmdPal.UI"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:coreViewModels="using:Microsoft.CmdPal.Core.ViewModels"
|
||||
xmlns:cpcontrols="using:Microsoft.CmdPal.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
|
||||
xmlns:local="using:Microsoft.CmdPal.UI"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
|
||||
x:Name="PageRoot"
|
||||
Background="Transparent"
|
||||
DataContext="{x:Bind ViewModel, Mode=OneWay}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
<!-- TODO: Figure out what we want to do here for filtering/grouping and where -->
|
||||
<!-- https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.data.collectionviewsource -->
|
||||
<!--<CollectionViewSource
|
||||
x:Name="ItemsCVS"
|
||||
IsSourceGrouped="True"
|
||||
Source="{x:Bind ViewModel.Items, Mode=OneWay}" />-->
|
||||
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:StringVisibilityConverter
|
||||
x:Key="StringVisibilityConverter"
|
||||
EmptyValue="Collapsed"
|
||||
NotEmptyValue="Visible" />
|
||||
<!--
|
||||
GridViewItemCornerRadius is the corner radius defined in GridView template; make
|
||||
it bigger to match the radii of the gallery
|
||||
-->
|
||||
<CornerRadius x:Key="GalleryGridViewItemContainerCornerRadius">6</CornerRadius>
|
||||
<CornerRadius x:Key="IconGridViewItemContainerCornerRadius">4</CornerRadius>
|
||||
<CornerRadius x:Key="GalleryGridViewItemRadius">4</CornerRadius>
|
||||
<CornerRadius x:Key="SmallGridViewItemCornerRadius">8</CornerRadius>
|
||||
<CornerRadius x:Key="MediumGridViewItemCornerRadius">8</CornerRadius>
|
||||
|
||||
<Style x:Key="IconGridViewItemStyle" TargetType="GridViewItem">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="GridViewItem">
|
||||
<ListViewItemPresenter
|
||||
x:Name="Root"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
CheckBoxBorderBrush="{ThemeResource GridViewItemCheckBoxBorderBrush}"
|
||||
CheckBoxBrush="{ThemeResource GridViewItemCheckBoxBrush}"
|
||||
CheckBoxCornerRadius="{ThemeResource GridViewItemCheckBoxCornerRadius}"
|
||||
CheckBoxDisabledBorderBrush="{ThemeResource GridViewItemCheckBoxDisabledBorderBrush}"
|
||||
CheckBoxDisabledBrush="{ThemeResource GridViewItemCheckBoxDisabledBrush}"
|
||||
CheckBoxPointerOverBorderBrush="{ThemeResource GridViewItemCheckBoxPointerOverBorderBrush}"
|
||||
CheckBoxPointerOverBrush="{ThemeResource GridViewItemCheckBoxPointerOverBrush}"
|
||||
CheckBoxPressedBorderBrush="{ThemeResource GridViewItemCheckBoxPressedBorderBrush}"
|
||||
CheckBoxPressedBrush="{ThemeResource GridViewItemCheckBoxPressedBrush}"
|
||||
CheckBoxSelectedBrush="{ThemeResource GridViewItemCheckBoxSelectedBrush}"
|
||||
CheckBoxSelectedDisabledBrush="{ThemeResource GridViewItemCheckBoxSelectedDisabledBrush}"
|
||||
CheckBoxSelectedPointerOverBrush="{ThemeResource GridViewItemCheckBoxSelectedPointerOverBrush}"
|
||||
CheckBoxSelectedPressedBrush="{ThemeResource GridViewItemCheckBoxSelectedPressedBrush}"
|
||||
CheckBrush="{ThemeResource GridViewItemCheckBrush}"
|
||||
CheckDisabledBrush="{ThemeResource GridViewItemCheckDisabledBrush}"
|
||||
CheckMode="{ThemeResource GridViewItemCheckMode}"
|
||||
CheckPressedBrush="{ThemeResource GridViewItemCheckPressedBrush}"
|
||||
ContentMargin="{TemplateBinding Padding}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}"
|
||||
Control.IsTemplateFocusTarget="True"
|
||||
CornerRadius="{StaticResource IconGridViewItemContainerCornerRadius}"
|
||||
DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}"
|
||||
DragBackground="{ThemeResource GridViewItemDragBackground}"
|
||||
DragForeground="{ThemeResource GridViewItemDragForeground}"
|
||||
DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}"
|
||||
FocusBorderBrush="{ThemeResource GridViewItemFocusBorderBrush}"
|
||||
FocusVisualMargin="{TemplateBinding FocusVisualMargin}"
|
||||
FocusVisualPrimaryBrush="{TemplateBinding FocusVisualPrimaryBrush}"
|
||||
FocusVisualPrimaryThickness="{TemplateBinding FocusVisualPrimaryThickness}"
|
||||
FocusVisualSecondaryBrush="{TemplateBinding FocusVisualSecondaryBrush}"
|
||||
FocusVisualSecondaryThickness="{TemplateBinding FocusVisualSecondaryThickness}"
|
||||
PlaceholderBackground="{ThemeResource GridViewItemPlaceholderBackground}"
|
||||
PointerOverBackground="{ThemeResource GridViewItemBackgroundPointerOver}"
|
||||
PointerOverBorderBrush="{ThemeResource GridViewItemPointerOverBorderBrush}"
|
||||
PointerOverForeground="{ThemeResource GridViewItemForegroundPointerOver}"
|
||||
PressedBackground="{ThemeResource GridViewItemBackgroundPressed}"
|
||||
ReorderHintOffset="{ThemeResource GridViewItemReorderHintThemeOffset}"
|
||||
SelectedBackground="{ThemeResource GridViewItemBackgroundSelected}"
|
||||
SelectedBorderBrush="{ThemeResource GridViewItemSelectedBorderBrush}"
|
||||
SelectedBorderThickness="{ThemeResource GridViewItemSelectedBorderThickness}"
|
||||
SelectedDisabledBackground="{ThemeResource GridViewItemBackgroundSelectedDisabled}"
|
||||
SelectedDisabledBorderBrush="{ThemeResource GridViewItemSelectedDisabledBorderBrush}"
|
||||
SelectedForeground="{ThemeResource GridViewItemForegroundSelected}"
|
||||
SelectedInnerBorderBrush="{ThemeResource GridViewItemSelectedInnerBorderBrush}"
|
||||
SelectedPointerOverBackground="{ThemeResource GridViewItemBackgroundSelectedPointerOver}"
|
||||
SelectedPointerOverBorderBrush="{ThemeResource GridViewItemSelectedPointerOverBorderBrush}"
|
||||
SelectedPressedBackground="{ThemeResource GridViewItemBackgroundSelectedPressed}"
|
||||
SelectedPressedBorderBrush="{ThemeResource GridViewItemSelectedPressedBorderBrush}"
|
||||
SelectionCheckMarkVisualEnabled="{ThemeResource GridViewItemSelectionCheckMarkVisualEnabled}" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="GalleryGridViewItemStyle" TargetType="GridViewItem">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="GridViewItem">
|
||||
<ListViewItemPresenter
|
||||
x:Name="Root"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
CheckBoxBorderBrush="{ThemeResource GridViewItemCheckBoxBorderBrush}"
|
||||
CheckBoxBrush="{ThemeResource GridViewItemCheckBoxBrush}"
|
||||
CheckBoxCornerRadius="{ThemeResource GridViewItemCheckBoxCornerRadius}"
|
||||
CheckBoxDisabledBorderBrush="{ThemeResource GridViewItemCheckBoxDisabledBorderBrush}"
|
||||
CheckBoxDisabledBrush="{ThemeResource GridViewItemCheckBoxDisabledBrush}"
|
||||
CheckBoxPointerOverBorderBrush="{ThemeResource GridViewItemCheckBoxPointerOverBorderBrush}"
|
||||
CheckBoxPointerOverBrush="{ThemeResource GridViewItemCheckBoxPointerOverBrush}"
|
||||
CheckBoxPressedBorderBrush="{ThemeResource GridViewItemCheckBoxPressedBorderBrush}"
|
||||
CheckBoxPressedBrush="{ThemeResource GridViewItemCheckBoxPressedBrush}"
|
||||
CheckBoxSelectedBrush="{ThemeResource GridViewItemCheckBoxSelectedBrush}"
|
||||
CheckBoxSelectedDisabledBrush="{ThemeResource GridViewItemCheckBoxSelectedDisabledBrush}"
|
||||
CheckBoxSelectedPointerOverBrush="{ThemeResource GridViewItemCheckBoxSelectedPointerOverBrush}"
|
||||
CheckBoxSelectedPressedBrush="{ThemeResource GridViewItemCheckBoxSelectedPressedBrush}"
|
||||
CheckBrush="{ThemeResource GridViewItemCheckBrush}"
|
||||
CheckDisabledBrush="{ThemeResource GridViewItemCheckDisabledBrush}"
|
||||
CheckMode="{ThemeResource GridViewItemCheckMode}"
|
||||
CheckPressedBrush="{ThemeResource GridViewItemCheckPressedBrush}"
|
||||
ContentMargin="{TemplateBinding Padding}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}"
|
||||
Control.IsTemplateFocusTarget="True"
|
||||
CornerRadius="{StaticResource GalleryGridViewItemContainerCornerRadius}"
|
||||
DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}"
|
||||
DragBackground="{ThemeResource GridViewItemDragBackground}"
|
||||
DragForeground="{ThemeResource GridViewItemDragForeground}"
|
||||
DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}"
|
||||
FocusBorderBrush="{ThemeResource GridViewItemFocusBorderBrush}"
|
||||
FocusVisualMargin="{TemplateBinding FocusVisualMargin}"
|
||||
FocusVisualPrimaryBrush="{TemplateBinding FocusVisualPrimaryBrush}"
|
||||
FocusVisualPrimaryThickness="{TemplateBinding FocusVisualPrimaryThickness}"
|
||||
FocusVisualSecondaryBrush="{TemplateBinding FocusVisualSecondaryBrush}"
|
||||
FocusVisualSecondaryThickness="{TemplateBinding FocusVisualSecondaryThickness}"
|
||||
PlaceholderBackground="{ThemeResource GridViewItemPlaceholderBackground}"
|
||||
PointerOverBackground="{ThemeResource GridViewItemBackgroundPointerOver}"
|
||||
PointerOverBorderBrush="{ThemeResource GridViewItemPointerOverBorderBrush}"
|
||||
PointerOverForeground="{ThemeResource GridViewItemForegroundPointerOver}"
|
||||
PressedBackground="{ThemeResource GridViewItemBackgroundPressed}"
|
||||
ReorderHintOffset="{ThemeResource GridViewItemReorderHintThemeOffset}"
|
||||
SelectedBackground="{ThemeResource GridViewItemBackgroundSelected}"
|
||||
SelectedBorderBrush="{ThemeResource GridViewItemSelectedBorderBrush}"
|
||||
SelectedBorderThickness="{ThemeResource GridViewItemSelectedBorderThickness}"
|
||||
SelectedDisabledBackground="{ThemeResource GridViewItemBackgroundSelectedDisabled}"
|
||||
SelectedDisabledBorderBrush="{ThemeResource GridViewItemSelectedDisabledBorderBrush}"
|
||||
SelectedForeground="{ThemeResource GridViewItemForegroundSelected}"
|
||||
SelectedInnerBorderBrush="{ThemeResource GridViewItemSelectedInnerBorderBrush}"
|
||||
SelectedPointerOverBackground="{ThemeResource GridViewItemBackgroundSelectedPointerOver}"
|
||||
SelectedPointerOverBorderBrush="{ThemeResource GridViewItemSelectedPointerOverBorderBrush}"
|
||||
SelectedPressedBackground="{ThemeResource GridViewItemBackgroundSelectedPressed}"
|
||||
SelectedPressedBorderBrush="{ThemeResource GridViewItemSelectedPressedBorderBrush}"
|
||||
SelectionCheckMarkVisualEnabled="{ThemeResource GridViewItemSelectionCheckMarkVisualEnabled}" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<DataTemplate x:Key="TagTemplate" x:DataType="coreViewModels:TagViewModel">
|
||||
<cpcontrols:Tag
|
||||
@@ -48,10 +166,17 @@
|
||||
x:Key="GridItemTemplateSelector"
|
||||
x:DataType="coreViewModels:ListItemViewModel"
|
||||
Gallery="{StaticResource GalleryGridItemViewModelTemplate}"
|
||||
GridProperties="{x:Bind ViewModel.GridProperties}"
|
||||
GridProperties="{x:Bind ViewModel.GridProperties, Mode=OneWay}"
|
||||
Medium="{StaticResource MediumGridItemViewModelTemplate}"
|
||||
Small="{StaticResource SmallGridItemViewModelTemplate}" />
|
||||
|
||||
<cmdpalUI:GridItemContainerStyleSelector
|
||||
x:Key="GridItemContainerStyleSelector"
|
||||
Gallery="{StaticResource GalleryGridViewItemStyle}"
|
||||
GridProperties="{x:Bind ViewModel.GridProperties, Mode=OneWay}"
|
||||
Medium="{StaticResource IconGridViewItemStyle}"
|
||||
Small="{StaticResource IconGridViewItemStyle}" />
|
||||
|
||||
<!-- https://learn.microsoft.com/windows/apps/design/controls/itemsview#specify-the-look-of-the-items -->
|
||||
<DataTemplate x:Key="ListItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
|
||||
<Grid
|
||||
@@ -94,7 +219,7 @@
|
||||
Text="{x:Bind Subtitle, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"
|
||||
Visibility="{x:Bind Subtitle, Mode=OneWay, Converter={StaticResource StringVisibilityConverter}}" />
|
||||
Visibility="{x:Bind ShowSubtitle, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
|
||||
<ItemsControl
|
||||
@@ -124,11 +249,11 @@
|
||||
Padding="8,16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="{x:Bind Title}"
|
||||
AutomationProperties.Name="{x:Bind Title, Mode=OneWay}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="8"
|
||||
CornerRadius="{StaticResource SmallGridViewItemCornerRadius}"
|
||||
Orientation="Vertical"
|
||||
ToolTipService.ToolTip="{x:Bind Title}">
|
||||
ToolTipService.ToolTip="{x:Bind Title, Mode=OneWay}">
|
||||
|
||||
<cpcontrols:IconBox
|
||||
x:Name="GridIconBorder"
|
||||
@@ -145,23 +270,22 @@
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="MediumGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
|
||||
<StackPanel
|
||||
<Grid
|
||||
Width="100"
|
||||
Height="100"
|
||||
Padding="8,16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="{x:Bind Title}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="8"
|
||||
Orientation="Vertical"
|
||||
ToolTipService.ToolTip="{x:Bind Title}">
|
||||
|
||||
Padding="8"
|
||||
AutomationProperties.Name="{x:Bind Title, Mode=OneWay}"
|
||||
CornerRadius="{StaticResource MediumGridViewItemCornerRadius}"
|
||||
ToolTipService.ToolTip="{x:Bind Title, Mode=OneWay}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<cpcontrols:IconBox
|
||||
x:Name="GridIconBorder"
|
||||
Grid.Row="0"
|
||||
Width="36"
|
||||
Height="36"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="12"
|
||||
@@ -169,21 +293,20 @@
|
||||
Foreground="{ThemeResource TextFillColorPrimary}"
|
||||
SourceKey="{x:Bind Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
|
||||
|
||||
<TextBlock
|
||||
x:Name="TitleTextBlock"
|
||||
MaxHeight="40"
|
||||
Margin="0,8,0,4"
|
||||
Grid.Row="1"
|
||||
Height="32"
|
||||
Margin="0,8,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="12"
|
||||
FontSize="12"
|
||||
Text="{x:Bind Title}"
|
||||
Text="{x:Bind Title, Mode=OneWay}"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="WordEllipsis"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{Binding ElementName=PageRoot, Path=DataContext.GridProperties.ShowTitle, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
</StackPanel>
|
||||
Visibility="{x:Bind LayoutShowsTitle, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="GalleryGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
|
||||
@@ -193,11 +316,11 @@
|
||||
Padding="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="{x:Bind Title}"
|
||||
AutomationProperties.Name="{x:Bind Title, Mode=OneWay}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="4"
|
||||
CornerRadius="{StaticResource GalleryGridViewItemRadius}"
|
||||
Orientation="Vertical"
|
||||
ToolTipService.ToolTip="{x:Bind Title}">
|
||||
ToolTipService.ToolTip="{x:Bind Title, Mode=OneWay}">
|
||||
|
||||
<Grid
|
||||
Width="160"
|
||||
@@ -205,12 +328,8 @@
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
CornerRadius="{StaticResource GalleryGridViewItemRadius}">
|
||||
<Viewbox
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
Stretch="UniformToFill"
|
||||
StretchDirection="Both">
|
||||
@@ -222,35 +341,39 @@
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Padding="4" Orientation="Vertical">
|
||||
<StackPanel
|
||||
Padding="4"
|
||||
Orientation="Vertical"
|
||||
Spacing="4"
|
||||
Visibility="{x:Bind help:BindTransformers.VisibleWhenAny(ShowTitle, ShowSubtitle)}">
|
||||
<TextBlock
|
||||
x:Name="TitleTextBlock"
|
||||
MaxWidth="152"
|
||||
MaxHeight="40"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="12"
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource TextFillColorPrimary}"
|
||||
Text="{x:Bind Title}"
|
||||
Text="{x:Bind Title, Mode=OneWay}"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="WordEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
TextWrapping="NoWrap"
|
||||
Visibility="{x:Bind ShowTitle, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
x:Name="SubTitleTextBlock"
|
||||
MaxWidth="152"
|
||||
MaxHeight="40"
|
||||
Margin="0,4,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="11"
|
||||
FontSize="11"
|
||||
Foreground="{ThemeResource TextFillColorTertiary}"
|
||||
Text="{x:Bind Subtitle}"
|
||||
Text="{x:Bind Subtitle, Mode=OneWay}"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="WordEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
TextWrapping="NoWrap"
|
||||
Visibility="{x:Bind ShowSubtitle, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
@@ -295,6 +418,7 @@
|
||||
IsDoubleTapEnabled="True"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="Items_ItemClick"
|
||||
ItemContainerStyleSelector="{StaticResource GridItemContainerStyleSelector}"
|
||||
ItemTemplateSelector="{StaticResource GridItemTemplateSelector}"
|
||||
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
|
||||
RightTapped="Items_RightTapped"
|
||||
@@ -302,6 +426,7 @@
|
||||
<GridView.ItemContainerTransitions>
|
||||
<TransitionCollection />
|
||||
</GridView.ItemContainerTransitions>
|
||||
<GridView.ItemContainerStyle />
|
||||
</GridView>
|
||||
</controls:Case>
|
||||
</controls:SwitchPresenter>
|
||||
|
||||
@@ -15,4 +15,7 @@ internal static class BindTransformers
|
||||
|
||||
public static Visibility EmptyOrWhitespaceToCollapsed(string? input)
|
||||
=> string.IsNullOrWhiteSpace(input) ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
public static Visibility VisibleWhenAny(bool value1, bool value2)
|
||||
=> (value1 || value2) ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
@@ -9,13 +9,6 @@ namespace SamplePagesExtension;
|
||||
|
||||
internal sealed partial class SampleGalleryListPage : ListPage
|
||||
{
|
||||
public SampleGalleryListPage()
|
||||
{
|
||||
Icon = new IconInfo("\uE7C5");
|
||||
Name = "Sample Gallery List Page";
|
||||
GridProperties = new GalleryGridLayout();
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
return [
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace SamplePagesExtension;
|
||||
|
||||
internal sealed partial class SampleGridsListPage : ListPage
|
||||
{
|
||||
private readonly IListItem[] _items =
|
||||
[
|
||||
new ListItem(new SampleGalleryListPage { GridProperties = new GalleryGridLayout { ShowTitle = true, ShowSubtitle = true } })
|
||||
{
|
||||
Title = "Gallery list page (title and subtitle)",
|
||||
Subtitle = "A sample gallery list page with images",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/Swirls.png"),
|
||||
},
|
||||
new ListItem(new SampleGalleryListPage { GridProperties = new GalleryGridLayout { ShowTitle = true, ShowSubtitle = false } })
|
||||
{
|
||||
Title = "Gallery list page (title, no subtitle)",
|
||||
Subtitle = "A sample gallery list page with images",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/Swirls.png"),
|
||||
},
|
||||
new ListItem(new SampleGalleryListPage { GridProperties = new GalleryGridLayout { ShowTitle = false, ShowSubtitle = false } })
|
||||
{
|
||||
Title = "Gallery list page (no title, no subtitle)",
|
||||
Subtitle = "A sample gallery list page with images",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/Swirls.png"),
|
||||
},
|
||||
new ListItem(new SampleGalleryListPage { GridProperties = new SmallGridLayout() })
|
||||
{
|
||||
Title = "Small grid list page",
|
||||
Subtitle = "A sample grid list page with text items",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/Win-Digital.png"),
|
||||
},
|
||||
new ListItem(new SampleGalleryListPage { GridProperties = new MediumGridLayout { ShowTitle = true } })
|
||||
{
|
||||
Title = "Medium grid (with title)",
|
||||
Subtitle = "A sample grid list page with text items",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/Win-Digital.png"),
|
||||
},
|
||||
new ListItem(new SampleGalleryListPage { GridProperties = new MediumGridLayout { ShowTitle = false } })
|
||||
{
|
||||
Title = "Medium grid (hidden title)",
|
||||
Subtitle = "A sample grid list page with text items",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/Win-Digital.png"),
|
||||
}
|
||||
];
|
||||
|
||||
public SampleGridsListPage()
|
||||
{
|
||||
Icon = new IconInfo("\uE7C5");
|
||||
Name = "Grid and gallery lists";
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems() => _items;
|
||||
}
|
||||
@@ -34,9 +34,9 @@ public partial class SamplesListPage : ListPage
|
||||
Title = "Dynamic List Page Command",
|
||||
Subtitle = "Changes the list of items in response to the typed query",
|
||||
},
|
||||
new ListItem(new SampleGalleryListPage())
|
||||
new ListItem(new SampleGridsListPage())
|
||||
{
|
||||
Title = "Gallery List Page Command",
|
||||
Title = "Grid views and galleries",
|
||||
Subtitle = "Displays items as a gallery",
|
||||
},
|
||||
new ListItem(new OnLoadPage())
|
||||
|
||||
Reference in New Issue
Block a user