Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions MainDemo.Wpf/Domain/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,15 @@ private static IEnumerable<DemoItem> GenerateDemoItems(ISnackbarMessageQueue sna
DocumentationLink.DemoPageLink<SmartHint>(),
DocumentationLink.StyleLink("SmartHint"),
});

yield return new DemoItem(
"PopupBox",
typeof(PopupBox),
new[]
{
DocumentationLink.DemoPageLink<PopupBox>(),
DocumentationLink.StyleLink("PopupBox"),
});
}

private bool DemoItemsFilter(object obj)
Expand Down
208 changes: 208 additions & 0 deletions MainDemo.Wpf/PopupBox.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
<UserControl x:Class="MaterialDesignDemo.PopupBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MaterialDesignDemo"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<ResourceDictionary>
<!-- This is needed to avoid runtime exceptions?! Seems like this might be a bug? -->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
</ResourceDictionary.MergedDictionaries>

<DataTemplate x:Key="ContentTemplateGrid">
<Grid Width="200" Height="100" TextElement.Foreground="{DynamicResource MaterialDesignLightForeground}">
<TextBlock Text="Popup content in grid" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>

<DataTemplate x:Key="ContentTemplateGridWithBackground">
<Grid Width="200" Height="100" Background="Fuchsia">
<TextBlock Text="Popup content in colored grid" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>

<DataTemplate x:Key="ContentTemplateButtonStack">
<!-- Margin of 10 here is to "make room" for the elevation drop shadows of the buttons. Must be compensated for in some (left/right) alignment scenarios using PopupBox.PopupHorizontalOffset -->
<StackPanel Margin="10">
<Button Content="1"
Opacity="0.5"
ToolTip="One with custom opacity" />
<Button Content="2" ToolTip="Two" />
<Button Content="3" ToolTip="Three" />
</StackPanel>
</DataTemplate>

<DataTemplate x:Key="ContentTemplateButtonStackWithBackground">
<Grid Background="Fuchsia">
<StackPanel Margin="10">
<Button Content="1"
Opacity="0.5"
ToolTip="One with custom opacity" />
<Button Content="2" ToolTip="Two" />
<Button Content="3" ToolTip="Three" />
</StackPanel>
</Grid>
</DataTemplate>

<x:Array x:Key="{x:Static local:PopupBox.DefaultStyleContentKey}" Type="ComboBoxItem">
<ComboBoxItem Tag="{StaticResource ContentTemplateGrid}" materialDesign:HintAssist.HelperText="Selected content works best when used with the MaterialDesignPopupBox style">Grid</ComboBoxItem>
<ComboBoxItem Tag="{StaticResource ContentTemplateGridWithBackground}">Colored grid</ComboBoxItem>
</x:Array>

<x:Array x:Key="{x:Static local:PopupBox.MultiFloatingActionStyleContentKey}" Type="ComboBoxItem">
<ComboBoxItem Tag="{StaticResource ContentTemplateButtonStack}" materialDesign:HintAssist.HelperText="Margin in the selected content (stack of buttons) needs to be compensated for in certain alignments (left/right)">Stack of buttons</ComboBoxItem>
<ComboBoxItem Tag="{StaticResource ContentTemplateButtonStackWithBackground}">Stack of buttons in colored grid</ComboBoxItem>
</x:Array>

<local:ComboBoxItemToDataTemplateConverter x:Key="ComboBoxItemToDataTemplateConverter" />
<local:ComboBoxItemToStyleConverter x:Key="ComboBoxItemToStyleConverter" />
<local:ComboBoxItemToHelperTextConverter x:Key="ComboBoxItemToHelperTextConverter" />

<Thickness x:Key="Spacer">0,30,0,0</Thickness>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="250" />
</Grid.ColumnDefinitions>

<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<TextBlock Grid.Row="0" HorizontalAlignment="Center" Style="{StaticResource MaterialDesignSubtitle2TextBlock}"
Text="{Binding ElementName=ContentComboBox, Path=SelectedItem, Converter={StaticResource ComboBoxItemToHelperTextConverter}}" />

<materialDesign:PopupBox Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center" SnapsToDevicePixels="True" Padding="0"
Style="{Binding ElementName=StyleComboBox, Path=SelectedItem, Converter={StaticResource ComboBoxItemToStyleConverter}}"
PopupElevation="{Binding ElementName=ElevationComboBox, Path=SelectedItem}"
PopupUniformCornerRadius="{Binding ElementName=UniformCornerRadiusComboBox, Path=SelectedItem}"
PopupHorizontalOffset="{Binding ElementName=HorizontalOffsetComboBox, Path=SelectedItem}"
PopupVerticalOffset="{Binding ElementName=VerticalOffsetComboBox, Path=SelectedItem}"
PlacementMode="{Binding ElementName=PopupBoxPlacementModeComboBox, Path=SelectedItem}">
<ContentControl ContentTemplate="{Binding ElementName=ContentComboBox, Path=SelectedItem, Converter={StaticResource ComboBoxItemToDataTemplateConverter}}" />
</materialDesign:PopupBox>
</Grid>

<StackPanel Grid.Column="1" Orientation="Vertical">
<GroupBox Header="Properties" Padding="10">
<StackPanel Orientation="Vertical">
<TextBlock Text="Style:" Style="{StaticResource MaterialDesignSubtitle2TextBlock}" />
<ComboBox x:Name="StyleComboBox" SelectionChanged="StyleComboBox_OnSelectionChanged">
<ComboBoxItem Tag="{StaticResource MaterialDesignPopupBox}">MaterialDesignPopupBox</ComboBoxItem>
<ComboBoxItem Tag="{StaticResource MaterialDesignMultiFloatingActionPopupBox}">MaterialDesignMultiFloatingActionPopupBox</ComboBoxItem>
</ComboBox>

<TextBlock Text="Elevation:" Margin="{StaticResource Spacer}" Style="{StaticResource MaterialDesignSubtitle2TextBlock}" />
<ComboBox x:Name="ElevationComboBox" SelectedIndex="6">
<materialDesign:Elevation>Dp0</materialDesign:Elevation>
<materialDesign:Elevation>Dp1</materialDesign:Elevation>
<materialDesign:Elevation>Dp2</materialDesign:Elevation>
<materialDesign:Elevation>Dp3</materialDesign:Elevation>
<materialDesign:Elevation>Dp4</materialDesign:Elevation>
<materialDesign:Elevation>Dp5</materialDesign:Elevation>
<materialDesign:Elevation>Dp6</materialDesign:Elevation>
<materialDesign:Elevation>Dp7</materialDesign:Elevation>
<materialDesign:Elevation>Dp8</materialDesign:Elevation>
<materialDesign:Elevation>Dp12</materialDesign:Elevation>
<materialDesign:Elevation>Dp16</materialDesign:Elevation>
<materialDesign:Elevation>Dp24</materialDesign:Elevation>
</ComboBox>

<TextBlock Text="PopupUniformCornerRadius:" Margin="{StaticResource Spacer}" Style="{StaticResource MaterialDesignSubtitle2TextBlock}" />
<ComboBox x:Name="UniformCornerRadiusComboBox" SelectedIndex="2">
<system:Double>0</system:Double>
<system:Double>2</system:Double>
<system:Double>4</system:Double>
<system:Double>6</system:Double>
<system:Double>8</system:Double>
<system:Double>10</system:Double>
<system:Double>20</system:Double>
</ComboBox>

<TextBlock Text="PopupHorizontalOffset:" Margin="{StaticResource Spacer}" Style="{StaticResource MaterialDesignSubtitle2TextBlock}" />
<ComboBox x:Name="HorizontalOffsetComboBox" SelectedIndex="10">
<system:Double>-100</system:Double>
<system:Double>-50</system:Double>
<system:Double>-20</system:Double>
<system:Double>-15</system:Double>
<system:Double>-10</system:Double>
<system:Double>-5</system:Double>
<system:Double>-4</system:Double>
<system:Double>-3</system:Double>
<system:Double>-2</system:Double>
<system:Double>-1</system:Double>
<system:Double>0</system:Double>
<system:Double>1</system:Double>
<system:Double>2</system:Double>
<system:Double>3</system:Double>
<system:Double>4</system:Double>
<system:Double>5</system:Double>
<system:Double>10</system:Double>
<system:Double>15</system:Double>
<system:Double>20</system:Double>
<system:Double>50</system:Double>
<system:Double>100</system:Double>
</ComboBox>

<TextBlock Text="PopupVerticalOffset:" Margin="{StaticResource Spacer}" Style="{StaticResource MaterialDesignSubtitle2TextBlock}" />
<ComboBox x:Name="VerticalOffsetComboBox" SelectedIndex="10">
<system:Double>-100</system:Double>
<system:Double>-50</system:Double>
<system:Double>-20</system:Double>
<system:Double>-15</system:Double>
<system:Double>-10</system:Double>
<system:Double>-5</system:Double>
<system:Double>-4</system:Double>
<system:Double>-3</system:Double>
<system:Double>-2</system:Double>
<system:Double>-1</system:Double>
<system:Double>0</system:Double>
<system:Double>1</system:Double>
<system:Double>2</system:Double>
<system:Double>3</system:Double>
<system:Double>4</system:Double>
<system:Double>5</system:Double>
<system:Double>10</system:Double>
<system:Double>15</system:Double>
<system:Double>20</system:Double>
<system:Double>50</system:Double>
<system:Double>100</system:Double>
</ComboBox>

<TextBlock Text="PopupBoxPlacementMode:" Margin="{StaticResource Spacer}" Style="{StaticResource MaterialDesignSubtitle2TextBlock}" />
<ComboBox x:Name="PopupBoxPlacementModeComboBox" SelectedIndex="1">
<materialDesign:PopupBoxPlacementMode>BottomAndAlignCentres</materialDesign:PopupBoxPlacementMode>
<materialDesign:PopupBoxPlacementMode>BottomAndAlignLeftEdges</materialDesign:PopupBoxPlacementMode>
<materialDesign:PopupBoxPlacementMode>BottomAndAlignRightEdges</materialDesign:PopupBoxPlacementMode>
<materialDesign:PopupBoxPlacementMode>LeftAndAlignBottomEdges</materialDesign:PopupBoxPlacementMode>
<materialDesign:PopupBoxPlacementMode>LeftAndAlignMiddles</materialDesign:PopupBoxPlacementMode>
<materialDesign:PopupBoxPlacementMode>LeftAndAlignTopEdges</materialDesign:PopupBoxPlacementMode>
<materialDesign:PopupBoxPlacementMode>RightAndAlignBottomEdges</materialDesign:PopupBoxPlacementMode>
<materialDesign:PopupBoxPlacementMode>RightAndAlignMiddles</materialDesign:PopupBoxPlacementMode>
<materialDesign:PopupBoxPlacementMode>RightAndAlignTopEdges</materialDesign:PopupBoxPlacementMode>
<materialDesign:PopupBoxPlacementMode>TopAndAlignCentres</materialDesign:PopupBoxPlacementMode>
<materialDesign:PopupBoxPlacementMode>TopAndAlignLeftEdges</materialDesign:PopupBoxPlacementMode>
<materialDesign:PopupBoxPlacementMode>TopAndAlignRightEdges</materialDesign:PopupBoxPlacementMode>
</ComboBox>
</StackPanel>
</GroupBox>

<GroupBox Header="Popup" Margin="{StaticResource Spacer}" Padding="10">
<StackPanel Orientation="Vertical">
<TextBlock Text="Popup Content:" Style="{StaticResource MaterialDesignSubtitle2TextBlock}" />
<ComboBox x:Name="ContentComboBox" />
</StackPanel>
</GroupBox>
</StackPanel>
</Grid>
</UserControl>
69 changes: 69 additions & 0 deletions MainDemo.Wpf/PopupBox.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.Collections;
using System.Globalization;
using System.Windows.Data;
using MaterialDesignThemes.Wpf;

namespace MaterialDesignDemo;

/// <summary>
/// Interaction logic for PopupBox.xaml
/// </summary>
public partial class PopupBox : UserControl
{
public const string DefaultStyleContentKey = nameof(DefaultStyleContentKey);
public const string MultiFloatingActionStyleContentKey = nameof(MultiFloatingActionStyleContentKey);

private readonly IEnumerable _defaultStyleContent;
private readonly IEnumerable _multiFloatingActionStyleContentKey;

public PopupBox()
{
InitializeComponent();

_defaultStyleContent = (IEnumerable)FindResource(DefaultStyleContentKey);
_multiFloatingActionStyleContentKey = (IEnumerable)FindResource(MultiFloatingActionStyleContentKey);

Loaded += (sender, args) => StyleComboBox.SelectedIndex = 0;
}

private void StyleComboBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBoxItem selectedItem = (ComboBoxItem) StyleComboBox.SelectedItem;
if (Equals(selectedItem.Content, "MaterialDesignPopupBox"))
{
ContentComboBox.ItemsSource = _defaultStyleContent;
}
else
{
ContentComboBox.ItemsSource = _multiFloatingActionStyleContentKey;
}
ContentComboBox.SelectedIndex = 0;
}
}

internal class ComboBoxItemToDataTemplateConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> value is ComboBoxItem { Tag: DataTemplate template} ? template : null;

public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotImplementedException();
}

internal class ComboBoxItemToStyleConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> value is ComboBoxItem { Tag: Style style } ? style : null;

public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotImplementedException();
}

internal class ComboBoxItemToHelperTextConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> value is ComboBoxItem item ? HintAssist.GetHelperText(item) : null!;

public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotImplementedException();
}
37 changes: 37 additions & 0 deletions MaterialDesignThemes.UITests/WPF/PopupBox/PopupBoxTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.ComponentModel;

namespace MaterialDesignThemes.UITests.WPF.PopupBox;

public class PopupBoxTests : TestBase
{
public PopupBoxTests(ITestOutputHelper output)
: base(output)
{ }

[Theory]
[InlineData(Elevation.Dp0)]
[InlineData(Elevation.Dp16)]
[InlineData(Elevation.Dp24)]
[Description("Issue 3129")]
public async Task PopupBox_WithElevation_AppliesElevationToNestedCard(Elevation elevation)
{
await using var recorder = new TestRecorder(App);

//Arrange
IVisualElement<Wpf.PopupBox> popupBox = await LoadXaml<Wpf.PopupBox>($@"
<materialDesign:PopupBox VerticalAlignment=""Top""
PopupElevation=""{elevation}"">
<StackPanel>
<Button Content=""More"" />
<Button Content=""Options"" />
</StackPanel>
</materialDesign:PopupBox>");

IVisualElement<Card> card = await popupBox.GetElement<Card>("/Card");

// Assert
Assert.Equal(elevation, await card.GetProperty<Elevation?>(ElevationAssist.ElevationProperty));

recorder.Success();
}
}
13 changes: 12 additions & 1 deletion MaterialDesignThemes.Wpf/Card.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ public double UniformCornerRadius
}
public static readonly DependencyProperty UniformCornerRadiusProperty
= DependencyProperty.Register(nameof(UniformCornerRadius), typeof(double), typeof(Card),
new FrameworkPropertyMetadata(DefaultUniformCornerRadius, FrameworkPropertyMetadataOptions.AffectsMeasure));
new FrameworkPropertyMetadata(DefaultUniformCornerRadius, FrameworkPropertyMetadataOptions.AffectsMeasure, UniformCornerRadiusChangedCallback));

private static void UniformCornerRadiusChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Card card = (Card) d;
card.UpdateContentClip();
}

#endregion

#region DependencyProperty : ContentClipProperty
Expand Down Expand Up @@ -59,7 +66,11 @@ public override void OnApplyTemplate()
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
UpdateContentClip();
}

private void UpdateContentClip()
{
if (_clipBorder is null)
{
return;
Expand Down
20 changes: 20 additions & 0 deletions MaterialDesignThemes.Wpf/DpiHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ public static double TransformToDeviceY(Visual visual, double y)
return TransformToDeviceY(y);
}

public static double TransformFromDeviceY(Visual visual, double y)
{
var source = PresentationSource.FromVisual(visual);
if (source?.CompositionTarget != null) return y / source.CompositionTarget.TransformToDevice.M22;

return TransformFromDeviceY(y);
}

public static double TransformToDeviceX(Visual visual, double x)
{
var source = PresentationSource.FromVisual(visual);
Expand All @@ -39,8 +47,20 @@ public static double TransformToDeviceX(Visual visual, double x)
return TransformToDeviceX(x);
}

public static double TransformFromDeviceX(Visual visual, double x)
{
var source = PresentationSource.FromVisual(visual);
if (source?.CompositionTarget != null) return x / source.CompositionTarget.TransformToDevice.M11;

return TransformFromDeviceX(x);
}

public static double TransformToDeviceY(double y) => y * DpiY / StandardDpiY;

public static double TransformFromDeviceY(double y) => y / DpiY * StandardDpiY;

public static double TransformToDeviceX(double x) => x * DpiX / StandardDpiX;

public static double TransformFromDeviceX(double x) => x / DpiX * StandardDpiX;
}
}
Loading