Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

13 Nov 2020

UWP的Dialogs和Flyouts

UWP文档阅读笔记。

Dialogs and flyouts

Dialog(对话框)和Flyout(飞出框)两者都是用于临时展示内容,对用户正在进行的操作都具有打扰性质。二者通常用于通知、请求以及展示一些额外的信息。

Dialog是模态的,会直接中断用户当前的操作,强迫用户完成和对话框的交互之后,才能继续之前的操作。

而Flyout则更为和谐一些,首先Flyout并不是模态的;其次,有很多方式可以撤销Flyout的显示,比如点击了Flyout之外的区域、敲了Escape按键或者回退键、改变窗口大小以及调整设备的显示方向。

何时使用Dialog和Flyout?

这个问题还是取决于它们是否有利于跟用户的交互。Dialog会对用户形成强制,除非必须,最好不用。而Flyout虽然和谐一些,但是过多显示会打扰用户。

下面是一些Dilaog的建议使用场景:

  • 显示重要的信息,用户必须立马知晓的信息
    • 当安全性可能遭破坏
    • 当一个有价值的项目将要被删除或者修改
    • 应用内购前的确认
  • 错误信息应当应用在整体级别,比如一个连结断掉的错误
  • 向用户提问,但是必须提供有意义的选项。

下面是一些Flyout的建议使用场景:

  • 为一个动作收集额外的信息
  • 显示一些临时性的信息,比如当用户点击一个缩写图的时候,在flyout里面显示更清晰的图片
  • 显示更多信息,比如一些细节信息或者更长的描述。

总的来说,要多问问自己是否真的选哟通过dialog和flyout来打断或者打扰用户。如果经常性需要显示dialog和flyout,要考虑是否在主界面上专门为这些信息设置一个区域。

Flyouts

从WinUI 2.2开始往后,Flyout的模板采用了圆角边框

和Flyout相似的有TooltipContext Menu,选择的时候需要甄别一下。

Flyout是附着在其他控件之上的,其Placement辖属可以用来指定flyout弹出的位置。对于Button这样的控件,提供了一个Flyout辖属,可以和context menu关联起来。

对于不自带Flyout辖属的控件,可以使用FlyoutBase.AttachedFlyout这个附加辖属来指定Flyout。相应的,可以使用FlyoutBase.ShowAttachedFlyout 来显示这个附加的Flyout。

Flyout并不直接从UIElement继承,所以是可共享的,可以作为静态资源,甚至在Style中定义。

Flyout的DataContext使其所附着的控件,所以下面的

<!-- Declare the shared flyout as a resource. -->
<Page.Resources>
    <Flyout x:Key="ImagePreviewFlyout" Placement="Right">
        <!-- The flyout's DataContext must be the Image Source
             of the image the flyout is attached to. -->
        <Image Source="{Binding Path=Source}"
            MaxHeight="400" MaxWidth="400" Stretch="Uniform"/>
    </Flyout>
</Page.Resources>

通过修改FlyoutPresenterStyle,可以调整Flyout的显示样式。下面是例子:

<Flyout>
  <Flyout.FlyoutPresenterStyle>
    <Style TargetType="FlyoutPresenter">
      <Setter Property="ScrollViewer.HorizontalScrollMode"
          Value="Disabled"/>
      <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
      <Setter Property="IsTabStop" Value="True"/>
      <Setter Property="TabNavigation" Value="Cycle"/>
    </Style>
  </Flyout.FlyoutPresenterStyle>
  <TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."/>
</Flyout>

在Xbox上显示的时候,Flyout会让附着的控件在显示上变暗一些。这个行为在其他平台上的Flyout默认是关闭的,但是可以通过LightDismissOverlayMode来控制呢。

菜单是一种对功能命令的索引。通过索引可以访问更多子菜单的命令。最常用的命令还是应该放置在主界面上。菜单的载体可以是MenuBar或者MenuFlyout,两者的应用场景有所不同。前者通常显示在界面顶部,后者则按需调出。

CommandBar也可以承载命令,但CommandBar只是为了显示少数的命令设计的,不像菜单那样支持多级分类。

Context Menu和普通Menu有所区别:

  • 如果一个载体元素的主要功能就是显示命令,那么可以用Menu来承载命令,button就是这样一个例子
  • 如果一个载体元素的主要功能不是显示命令,比如textbbox的主要功能是为了文字显示和输入,那么就使用context menu作为辅助手段来承载命令。

下面是更多关于Context Menu和Menu的对比:

Menu

  • 有一个单一入口点
  • 通常由按键触发,或者作为子菜单嵌套到其他菜单中。
  • 通常使用左键点击(或者手指单点)触发
  • 关联到Flyout或者FlyoutBase.AttachedFlyout

Context Menu

  • 附着到单一的元素,并且用于显示次级命令
  • 通常由右键点击(或者手指按停)触发
  • 关联到ContextFlyout

菜单项常常伴随着图标。考虑为下列场景提供图标:

  • 常用项目
  • 广泛使用的
  • 含义显而易见的

简单来说图标要简单明了。

可以使用MenuFlyout来创建menu flyout和context menu。菜单项可以是MenuFlyoutItem, MenuFlyoutSubItem, ToggleMenuFlyoutItem, RadioMenuFlyoutItem:

  • MenuFlyoutItem,调用一个动作
  • MenuFlyoutSubItem,用于嵌套子菜单
  • ToggleMenuFlyoutItem,可以让菜单项拥有开合状态
  • RadioMenuFlyoutItem,在多个菜单项之间选中一个项目
  • MenuFlyoutSeparater,分隔菜单项

Menu和ContextMenu这种弹出形式都属于Light dimiss control。它们的Overlay模式可以由LightDismissOverlayMode辖属控制。不同平台这个辖属的默认值可能不同。

Tooltip

Tooltip是一段对其他控件或者对象的简短描述,可以帮助用户熟悉目标对象。通常Tooltip在鼠标悬停的时候自动显示,过段时间又自行消失。

对于是否需要使用Tooltip,可以根据下列问题来判断:

Tooltip适用于下列问题:

  • 是否需要鼠标悬停的时候显示信息
  • 是否需要给一个控件加上额外标签
  • 是否显示额外信息会给一个对象带来益处

Tooltip不适用于下列问题:

  • 额外信息是否是error, warning或者status?这些情况下,最好使用其他控件,比如flyout
  • 是否需要交互?Tooltip并不具备交互性
  • 是否需要打印?打印时Tooltip不显示
  • 是否会打扰到用户?如果是,则要少显示或者不显示,甚至通过选项来关闭。

ToolTip通过ToolTipService.ToolTip附加辖属提供:

<Button Content="Submit" ToolTipService.ToolTip="Click to submit"/>

在代码中的话:

ToolTip toolTip = new ToolTip();
toolTip.Content = "Click to submit";
ToolTipService.SetToolTip(submitButton, toolTip);

可以使用任意对象来作为ToolTip的内容:

<TextBlock Text="store logo">
    <ToolTipService.ToolTip>
        <Image Source="Assets/StoreLogo.png"/>
    </ToolTipService.ToolTip>
</TextBlock>

通过调整ToolTipService.Placement以及PlacementRect 可以改变ToolTip的显示位置。

文档中还附带了一些其他建议,就不在此处一一列举了。

Dialogs article

Dialog是一种模态UI叠层,用于显示上下文相关相关信息。

一些使用Dialog得指导原则:

  • 在Dialog文本的第一行中清晰指明问题
  • Dialog标题是可选的,用户描述指令
    • 使用简短的描述来告诉用户该做什么
    • 如果使用对话框来告知简单信息、错误或者问题,标题可以忽略
    • 确保标题的指示和按键的行为是一致的
  • Dialog的内容部分是必须的,此处应当给出更详细的描述
  • 至少要显示一个按键
    • 至少要有一个按键指向“安全”的操作,例子“Got it!",”Close“, 或者”Cancel“
    • 使用确定的问答作为Button文本的内容,比如在对于问题”Do you want to allow AppName to access your location?“,按键文本可以有”Allow“和”Block“。
    • 确保按键文本是简洁的,让用户可以快速做决定
    • 除了上面提到的”安全“按键之外,还可以有执行操作的按键,比如”do it“。但是这个按键必须排在左边。
  • 对于页面特殊位置的错误,比如输入框验证失败,不应该使用对话框来报告
  • 使用ContentDialog,而不是废弃的MessageDialog

默认情况下,Content dialog的显示位置是相对于ApplicationView的。但是在一个AppWindow或者XAML Island中使用,必须显示设置XamlRoot辖属:

private async void DisplayNoWifiDialog()
{
    ContentDialog noWifiDialog = new ContentDialog
    {
        Title = "No wifi connection",
        Content = "Check your connection and try again.",
        CloseButtonText = "Ok"
    };

    // Use this code to associate the dialog to the appropriate AppWindow by setting
    // the dialog's XamlRoot to the same XamlRoot as an element that is already present in the AppWindow.
    if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
    {
        noWifiDialog.XamlRoot = elementAlreadyInMyAppWindow.XamlRoot;
    }

    ContentDialogResult result = await noWifiDialog.ShowAsync();
}

AppWindow和XamlIsland都是Window 10,1903中引入的,使用的时候要检查平台的版本,查看Version adaptive apps

There can only be one ContentDialog open per thread at a time. Attempting to open two ContentDialogs will throw an exception, even if they are attempting to open in separate AppWindows. 另外在XAML中声明的ContentDialog默认不显示,只有在代码中调用ShowAsync的时候才会出现。

其他相关

FlyoutBase

FlyoutBase是直接从DependencyObject派生出来的,它的派生类包括

  • CommandBarFlyout
  • Flyout
  • MenuFlyout
  • Primitives.PickerFlyout

FlyoutShowOptions可以用来控制Flyout的显示选项。

UIElement.ContextRequested Event

UIElement.ContextRequested Event和对应的ContextCanceled可以用来自定义UIElement的上下文控制。 具体查看https://github.com/microsoft/Windows-universal-samples/blob/master/Samples/XamlContextMenu/shared/Scenario3.xaml

[UWP] How can I get the clicked item from the RightTapped event on a ListView?

What I did was handle the right-click in the datatemplate, and then the following code gets me the actual item that was right-clicked:

    private void StackPanel_RightTapped(object sender, RightTappedRoutedEventArgs e)
        {
            var s = (FrameworkElement) sender;
            var d = s.DataContext;
        }

How use ContextFlyout in a StackPanel in UWP?

ContextFlyout property is available since the introduced version 3.0, version 10.0.14393.0. What you need is to check your API contract version and device family version.

For API contract version 1.0/2.0, as @Igor Damiani suggested, you can use FlyoutBase.AttachedFlyout, and you can get the DataContext for example in the RightTapped event of the StackPanel:

UWP Grid.ContextFlyout not opening,另一个使用AttachedFlyout替代ContextFlyout的例子。

ItemsControl.ItemFromContainer 可以将从ItemContainer(也就是ListViewItem)获取相应的Item。这可以解决How do you use the ContextFlyout with a ListView?中的问题。

杂项

(本篇完)

Categories

Tags

comments powered by Disqus