Get UI building with Framework 4.0

This commit is contained in:
Matt Nadareski
2023-11-23 01:19:17 -05:00
parent ee4762f8b3
commit b848a401f8
4 changed files with 268 additions and 57 deletions

View File

@@ -34,6 +34,7 @@
- Update compatibility libraries
- Support .NET Framework 2.0
- Support .NET Framework 3.5 in UI.Core
- Get UI building with Framework 4.0
### 3.0.0 (2023-11-14)

View File

@@ -211,42 +211,17 @@
<ColumnDefinition Width="*"/>
<ColumnDefinition MinWidth="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" Width="0"/>
</Grid.ColumnDefinitions>
<Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2" IsOpen="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
<Themes:SystemDropShadowChrome x:Name="shadow" Color="Transparent" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{Binding ActualWidth, ElementName=templateRoot}">
<Border x:Name="dropDownBorder" BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
<ScrollViewer x:Name="DropDownScrollViewer">
<Grid x:Name="grid" RenderOptions.ClearTypeHint="Enabled">
<Canvas x:Name="canvas" HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
<Rectangle x:Name="opaqueRect" Fill="{Binding Background, ElementName=dropDownBorder}" Height="{Binding ActualHeight, ElementName=dropDownBorder}" Width="{Binding ActualWidth, ElementName=dropDownBorder}"/>
</Canvas>
<ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Grid>
</ScrollViewer>
</Border>
</Themes:SystemDropShadowChrome>
</Popup>
<Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2" IsOpen="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom" />
<ToggleButton x:Name="toggleButton" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.ColumnSpan="2" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Style="{DynamicResource ComboBoxToggleButton}"/>
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" Content="{TemplateBinding SelectionBoxItem}" ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" IsHitTestVisible="false" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true">
<Setter Property="Margin" TargetName="shadow" Value="0,0,5,5"/>
<Setter Property="Color" TargetName="shadow" Value="#71000000"/>
</Trigger>
<Trigger Property="HasItems" Value="false">
<Setter Property="Height" TargetName="dropDownBorder" Value="95"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsGrouping" Value="true"/>
<Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</MultiTrigger>
<Trigger Property="ScrollViewer.CanContentScroll" SourceName="DropDownScrollViewer" Value="false">
<Setter Property="Canvas.Top" TargetName="opaqueRect" Value="{Binding VerticalOffset, ElementName=DropDownScrollViewer}"/>
<Setter Property="Canvas.Left" TargetName="opaqueRect" Value="{Binding HorizontalOffset, ElementName=DropDownScrollViewer}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<SolidColorBrush x:Key="TextBox.Static.Background" Color="#FFFFFFFF"/>
@@ -272,20 +247,7 @@
<ColumnDefinition Width="*"/>
<ColumnDefinition MinWidth="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" Width="0"/>
</Grid.ColumnDefinitions>
<Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2" IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
<Themes:SystemDropShadowChrome x:Name="shadow" Color="Transparent" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{Binding ActualWidth, ElementName=templateRoot}">
<Border x:Name="dropDownBorder" BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
<ScrollViewer x:Name="DropDownScrollViewer">
<Grid x:Name="grid" RenderOptions.ClearTypeHint="Enabled">
<Canvas x:Name="canvas" HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
<Rectangle x:Name="opaqueRect" Fill="{Binding Background, ElementName=dropDownBorder}" Height="{Binding ActualHeight, ElementName=dropDownBorder}" Width="{Binding ActualWidth, ElementName=dropDownBorder}"/>
</Canvas>
<ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Grid>
</ScrollViewer>
</Border>
</Themes:SystemDropShadowChrome>
</Popup>
<Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2" IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom" />
<ToggleButton x:Name="toggleButton" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.ColumnSpan="2" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Style="{DynamicResource ComboBoxToggleButton}"/>
<Border x:Name="border" Background="{DynamicResource TextBox.Static.Background}" Margin="{TemplateBinding BorderThickness}">
<TextBox x:Name="PART_EditableTextBox" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" IsReadOnly="{Binding IsReadOnly, RelativeSource={RelativeSource TemplatedParent}}" Margin="{TemplateBinding Padding}" Style="{DynamicResource ComboBoxEditableTextBox}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
@@ -298,24 +260,12 @@
<Trigger Property="IsKeyboardFocusWithin" Value="true">
<Setter Property="Foreground" Value="Black"/>
</Trigger>
<Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true">
<Setter Property="Margin" TargetName="shadow" Value="0,0,5,5"/>
<Setter Property="Color" TargetName="shadow" Value="#71000000"/>
</Trigger>
<Trigger Property="HasItems" Value="false">
<Setter Property="Height" TargetName="dropDownBorder" Value="95"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsGrouping" Value="true"/>
<Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</MultiTrigger>
<Trigger Property="ScrollViewer.CanContentScroll" SourceName="DropDownScrollViewer" Value="false">
<Setter Property="Canvas.Top" TargetName="opaqueRect" Value="{Binding VerticalOffset, ElementName=DropDownScrollViewer}"/>
<Setter Property="Canvas.Left" TargetName="opaqueRect" Value="{Binding HorizontalOffset, ElementName=DropDownScrollViewer}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="CustomComboBoxStyle" TargetType="{x:Type ComboBox}">

View File

@@ -1,7 +1,221 @@
namespace MPF
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using Microsoft.Windows.Themes;
namespace MPF
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : System.Windows.Application { }
public partial class App : Application
{
#if NET40_OR_GREATER || NETCOREAPP
public App()
{
InitializeComponent();
AddDropShadowChrome("ComboBoxTemplate");
AddDropShadowChrome("ComboBoxEditableTemplate");
#if NET452_OR_GREATER || NETCOREAPP
AddVirtualizingCondition("ComboBoxTemplate");
AddVirtualizingCondition("ComboBoxEditableTemplate");
#endif
}
private void AddDropShadowChrome(string resourceName)
{
// Get the template resource
if (Resources[resourceName] is not ControlTemplate controlTemplate)
return;
// Get the popup
if (controlTemplate.Resources["PART_Popup"] is not Popup popup)
return;
// Create the various nested items
var chrome = new SystemDropShadowChrome
{
Name = "shadow",
Color = Colors.Transparent,
};
var border = new Border
{
Name = "dropDownBorder",
BorderThickness = new Thickness(1),
};
var scrollViewer = new ScrollViewer { Name = "DropDownScrollViewer" };
var grid = new Grid { Name = "grid" };
RenderOptions.SetClearTypeHint(grid, ClearTypeHint.Enabled);
var canvas = new Canvas
{
Name = "canvas",
HorizontalAlignment = HorizontalAlignment.Left,
Height = 0,
VerticalAlignment = VerticalAlignment.Top,
Width = 0,
};
var rectangle = new Rectangle { Name = "opaqueRect" };
var itemsPresenter = new ItemsPresenter { Name = "ItemsPresenter" };
KeyboardNavigation.SetDirectionalNavigation(itemsPresenter, KeyboardNavigationMode.Contained);
// Set the bindings
BindingOperations.SetBinding(chrome, SystemDropShadowChrome.MaxHeightProperty, new Binding
{
Source = new TemplateBindingExtension(ComboBox.MaxDropDownHeightProperty),
});
BindingOperations.SetBinding(chrome, SystemDropShadowChrome.MinWidthProperty, new Binding
{
Source = ComboBox.ActualWidthProperty,
ElementName = "templateRoot",
});
BindingOperations.SetBinding(border, Border.BorderBrushProperty, new Binding
{
Source = new DynamicResourceExtension(new StaticResourceExtension(SystemColors.WindowFrameBrushKey)),
});
BindingOperations.SetBinding(border, Border.BackgroundProperty, new Binding
{
Source = new DynamicResourceExtension(new StaticResourceExtension(SystemColors.WindowBrushKey)),
});
BindingOperations.SetBinding(rectangle, Rectangle.FillProperty, new Binding
{
Source = Border.BackgroundProperty,
ElementName = "dropDownBorder",
});
BindingOperations.SetBinding(rectangle, Rectangle.HeightProperty, new Binding
{
Source = Border.ActualHeightProperty,
ElementName = "dropDownBorder",
});
BindingOperations.SetBinding(rectangle, Rectangle.WidthProperty, new Binding
{
Source = Border.ActualWidthProperty,
ElementName = "dropDownBorder",
});
BindingOperations.SetBinding(itemsPresenter, ItemsPresenter.SnapsToDevicePixelsProperty, new Binding
{
Source = new TemplateBindingExtension(ComboBox.SnapsToDevicePixelsProperty),
});
// Build the item tree
canvas.Children.Add(rectangle);
grid.Children.Add(canvas);
grid.Children.Add(itemsPresenter);
scrollViewer.Content = grid;
border.Child = scrollViewer;
chrome.Child = border;
// Add the tree to the popup
popup.Child = chrome;
// Get the triggers
var triggers = controlTemplate.Triggers;
if (triggers == null)
return;
// Create the new triggers
var hasDropShadow = new Trigger
{
Property = Popup.HasDropShadowProperty,
SourceName = "PART_Popup",
Value = "true",
};
var hasItems = new Trigger
{
Property = ComboBox.HasItemsProperty,
Value = "false",
};
var canContentScroll = new Trigger
{
Property = ScrollViewer.CanContentScrollProperty,
SourceName = "DropDownScrollViewer",
Value = "false",
};
// Create and add the setters
hasDropShadow.Setters.Add(new Setter
{
Property = SystemDropShadowChrome.MarginProperty,
TargetName = "shadow",
Value = "0,0,5,5",
});
hasDropShadow.Setters.Add(new Setter
{
Property = SystemDropShadowChrome.ColorProperty,
TargetName = "shadow",
Value = "#71000000",
});
hasItems.Setters.Add(new Setter
{
Property = Border.HeightProperty,
TargetName = "dropDownBorder",
Value = "95",
});
canContentScroll.Setters.Add(new Setter
{
Property = Canvas.TopProperty,
TargetName = "opaqueRect",
Value = new Binding
{
Source = ScrollViewer.VerticalOffsetProperty,
ElementName = "DropDownScrollViewer",
},
});
canContentScroll.Setters.Add(new Setter
{
Property = Canvas.LeftProperty,
TargetName = "opaqueRect",
Value = new Binding
{
Source = ScrollViewer.HorizontalOffsetProperty,
ElementName = "DropDownScrollViewer",
},
});
// Add the new triggers
triggers.Add(hasDropShadow);
triggers.Add(hasItems);
triggers.Add(canContentScroll);
}
#if NET452_OR_GREATER || NETCOREAPP
private void AddVirtualizingCondition(string resourceName)
{
// Get the template resource
if (Resources[resourceName] is not ControlTemplate controlTemplate)
return;
// Get the triggers
var triggers = controlTemplate.Triggers;
if (triggers == null)
return;
// Get the multitrigger
if (triggers.FirstOrDefault(t => t is MultiTrigger) is not MultiTrigger multiTrigger)
return;
// Set the virtualizing condition
var condition = new Condition
{
Property = VirtualizingPanel.IsVirtualizingWhenGroupingProperty,
Value = "false",
};
multiTrigger.Conditions.Add(condition);
}
#endif
#endif
}
}

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net452;net462;net472;net48;netcoreapp3.1;net5.0-windows;net6.0-windows;net7.0-windows;net8.0-windows</TargetFrameworks>
<TargetFrameworks>net40;net452;net462;net472;net48;netcoreapp3.1;net5.0-windows;net6.0-windows;net7.0-windows;net8.0-windows</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
<OutputType>WinExe</OutputType>
<ApplicationIcon>Images\Icon.ico</ApplicationIcon>
@@ -24,6 +24,16 @@
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<!-- Special handling for Aero2 on non-Framework .NET -->
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`)) OR $(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
<ReferenceWpfAeroTheme>false</ReferenceWpfAeroTheme>
<ReferenceWpfAero2Theme>true</ReferenceWpfAero2Theme>
<ReferenceWpfAeroLiteTheme>false</ReferenceWpfAeroLiteTheme>
<ReferenceWpfClassicTheme>false</ReferenceWpfClassicTheme>
<ReferenceWpfLunaTheme>false</ReferenceWpfLunaTheme>
<ReferenceWpfRoyaleTheme>false</ReferenceWpfRoyaleTheme>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="MPF.Test" />
</ItemGroup>
@@ -37,9 +47,12 @@
<ProjectReference Include="..\MPF.UI.Core\MPF.UI.Core.csproj" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith(`net4`))">
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
<Reference Include="PresentationFramework.Aero" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`)) OR $(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
<Reference Include="PresentationFramework.Aero2" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.0.2" GeneratePathProperty="true">
@@ -52,4 +65,37 @@
<Content Include="$(PkgBinaryObjectScanner)\content\**" PackagePath="contentFiles\any\any;content" CopyToOutputDirectory="Always" PackageCopyToOutput="true" />
</ItemGroup>
</Project>
<!-- Special handling for Aero2 on non-Framework .NET -->
<Target Name="SelectWpfThemeAssembly" AfterTargets="ResolveAssemblyReferences" Condition="'$(ReferenceWpfAeroTheme)' == 'true' Or&#xD;&#xA; '$(ReferenceWpfAero2Theme)' == 'true' Or&#xD;&#xA; '$(ReferenceWpfAeroLiteTheme)' == 'true' Or&#xD;&#xA; '$(ReferenceWpfClassicTheme)' == 'true' Or &#xD;&#xA; '$(ReferenceWpfLunaTheme)' == 'true' Or&#xD;&#xA; '$(ReferenceWpfRoyaleTheme)' == 'true'">
<ItemGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`)) OR $(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
<_WpfThemeAssemblies Include="@(ReferencePath)" Condition="'%(ReferencePath.NuGetPackageId)'=='Microsoft.WindowsDesktop.App.Ref' And &#xD;&#xA; '%(ReferencePath.FileName)'=='PresentationFramework.Aero'">
<Aliases Condition="'$(WpfAeroThemeAliases)'!=''">$(WpfAeroThemeAliases)</Aliases>
</_WpfThemeAssemblies>
<_WpfThemeAssemblies Include="@(ReferencePath)" Condition="'%(ReferencePath.NuGetPackageId)'=='Microsoft.WindowsDesktop.App.Ref' And &#xD;&#xA; '%(ReferencePath.FileName)'=='PresentationFramework.Aero2'">
<Aliases Condition="'$(WpfAero2ThemeAliases)'!=''">$(WpfAero2ThemeAliases)</Aliases>
</_WpfThemeAssemblies>
<_WpfThemeAssemblies Include="@(ReferencePath)" Condition="'%(ReferencePath.NuGetPackageId)'=='Microsoft.WindowsDesktop.App.Ref' And &#xD;&#xA; '%(ReferencePath.FileName)'=='PresentationFramework.AeroLite'">
<Aliases Condition="'$(WpfAeroLiteThemeAliases)'!=''">$(WpfAeroLiteThemeAliases)</Aliases>
</_WpfThemeAssemblies>
<_WpfThemeAssemblies Include="@(ReferencePath)" Condition="'%(ReferencePath.NuGetPackageId)'=='Microsoft.WindowsDesktop.App.Ref' And &#xD;&#xA; '%(ReferencePath.FileName)'=='PresentationFramework.Classic'">
<Aliases Condition="'$(WpfClassicThemeAliases)'!=''">$(WpfClassicThemeAliases)</Aliases>
</_WpfThemeAssemblies>
<_WpfThemeAssemblies Include="@(ReferencePath)" Condition="'%(ReferencePath.NuGetPackageId)'=='Microsoft.WindowsDesktop.App.Ref' And &#xD;&#xA; '%(ReferencePath.FileName)'=='PresentationFramework.Luna'">
<Aliases Condition="'$(WpfLunaThemeAliases)'!=''">$(WpfLunaThemeAliases)</Aliases>
</_WpfThemeAssemblies>
<_WpfThemeAssemblies Include="@(ReferencePath)" Condition="'%(ReferencePath.NuGetPackageId)'=='Microsoft.WindowsDesktop.App.Ref' And &#xD;&#xA; '%(ReferencePath.FileName)'=='PresentationFramework.Royale'">
<Aliases Condition="'$(WpfRoyaleThemeAliases)'!=''">$(WpfRoyaleThemeAliases)</Aliases>
</_WpfThemeAssemblies>
<ReferencePath Remove="@(_WpfThemeAssemblies)" />
<ReferencePath Include="@(_WpfThemeAssemblies)" Condition="('%(_WpfThemeAssemblies.FileName)'=='PresentationFramework.Aero' And '$(ReferenceWpfAeroTheme)'=='true') Or &#xD;&#xA; ('%(_WpfThemeAssemblies.FileName)'=='PresentationFramework.Aero2' And '$(ReferenceWpfAero2Theme)'=='true') Or &#xD;&#xA; ('%(_WpfThemeAssemblies.FileName)'=='PresentationFramework.AeroLite' And '$(ReferenceWpfAeroLiteTheme)'=='true') Or &#xD;&#xA; ('%(_WpfThemeAssemblies.FileName)'=='PresentationFramework.Classic' And '$(ReferenceWpfClassicTheme)'=='true') Or &#xD;&#xA; ('%(_WpfThemeAssemblies.FileName)'=='PresentationFramework.Luna' And '$(ReferenceWpfLunaTheme)'=='true') Or&#xD;&#xA; ('%(_WpfThemeAssemblies.FileName)'=='PresentationFramework.Royale' And '$(ReferenceWpfRoyaleTheme)'=='true')" />
</ItemGroup>
</Target>
</Project>