正确地更新媒体资源,以使加载不会停止UI的正确方法?

时间:2018-12-14 09:49:53

标签: c# wpf asynchronous task

很抱歉,标题是我能想到的最好的。让我解释一下我的问题:我有一个WPF应用程序,它的菜单看起来很像您的标准顶部菜单,它只占屏幕的5%。单击菜单项,下面的视图将更改。菜单控件是自制的,其原因如下:动态更改菜单项,不适合现有控件的怪异UI等。

使用绑定到“ CurrentMenuView”属性的ContentControl完成菜单更改视图部分。单击菜单项时,会发生这种情况(伪代码):

private async void Button_Pressed(...)
{
   await MakeSomeVisualMenuChanges();
   CurrentMenuView = new CarsView();
   await MakeSomOtherStuff();
}

问题在于某些视图需要花费一些时间来加载,这使得其他步骤的执行速度也变慢。我想开始加载“ CarsView”,但同时(不要之后)继续进行其他更改。这样用户可以看到正在发生的事情。

我可以使用Task.Run()解决此问题,但这似乎是错误的,因为它将内容放置在不同的上下文中。

解决这个问题的正确/更好的方法是什么?

编辑:

感谢所有答案,因为我希望这不容易解释。让我尝试一个简单的示例:

MainWindows.xaml:

<Window x:Class="WpfApp32.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid ShowGridLines="True">
        <Grid.RowDefinitions>
            <RowDefinition Height="30" />
            <RowDefinition Height="300" />
            <RowDefinition Height="30" />
        </Grid.RowDefinitions>

        <Button Grid.Row="0" Width="50" Content="Click" Click="Button_Click"  />
        <ContentControl Grid.Row="1" Width="200" Height="200"  Content="{Binding PageContent, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
        <TextBlock Grid.Row="2" Text="Bottom" Width="300" x:Name="BottomText" />
    </Grid>
</Window>

隐藏代码:

using System.Windows;
using System.ComponentModel;

namespace WpfApp32
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            BottomText.Text = "AndNowforSomethingCompletelyDifferent";
            PageContent = new UserControl1();
        }

        private object pageContent;
        public object PageContent
        {
            get => pageContent;
            set
            {
                pageContent = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PageContent)));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

    }
}

UserControl1.xaml:

<UserControl x:Class="WpfApp32.UserControl1"
             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" 
             mc:Ignorable="d" 
             Loaded="UserControl_Loaded"
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <TextBlock x:Name="CtrlTextBlock" Width="100"/>
    </Grid>
</UserControl>

UserControl1.cs:

using System.Windows;
using System.Windows.Controls;

namespace WpfApp32
{
    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
        }

        private void Init()
        {
            System.Threading.Thread.Sleep(2000);
            CtrlTextBlock.Text = "Loaded";
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            Init();
        }
    }
}

因此,这已从异步等中删除,仅显示了问题。当按下按钮时,UserControl1已加载,但加载需要2秒钟。在此之前,“ BottomText”元素中的文本保持不变。

正如我之前所说,我可以通过在按钮单击中执行以下操作来解决此问题:

private void Button_Click(object sender, RoutedEventArgs e)
{
    BottomText.Text = "AndNowforSomethingCompletelyDifferent";
    System.Threading.Tasks.Task.Run(() => Application.Current.Dispatcher.Invoke(() => PageContent = new UserControl1()));
}

但不确定这是要走的路。因此,这里的基本问题是将ContentControl绑定到属性,并且设置该属性可能需要一些时间。在加载的同时,我不想停止MainWindow中的执行(我希望BottomText元素立即显示“ AndNowforSomethingCompletelyDifferent”)。

1 个答案:

答案 0 :(得分:1)

这是一个模拟3秒钟加载的示例...这不是一个非常复杂的UI,绑定到复杂的DataTemplates(尤其是嵌套的)可能会使速度变慢,但是通常,拖拽来自拉动数据。

诀窍是拥有一个聪明的UI,该UI可以使事情继续前进,同时还可以让用户知道它正在等待其他事情继续下去。例如,臭名昭著的加载栏...不是我会使用确切的过程,但这是正确的主意。

注意和免责声明:除非在某种类型的自定义控件中,否则我几乎会鄙视代码。永远都看不到。我总是更喜欢MVVM并使用ViewModel进行绑定;但不能仅针对UI使用的数据来构建或控制UI。这么说是因为这个例子不是全部。我只是简单地在视图上制作了控件,并通过示例尽可能简单地将代码放在后面以回答这个问题。

景观

<Window x:Class="Question_Answer_WPF_App.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Height="500"
        Width="800">
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="30" />
            <RowDefinition />
        </Grid.RowDefinitions>

        <ToggleButton x:Name="menuButton"
                      Content="Menu"
                      Click="MenuButton_Click" />

        <!--I do not recommend binding in the view like this... Make a custom control that does this properly.-->
        <Grid x:Name="menu"
              Visibility="{Binding IsChecked, Converter={StaticResource BooleanToVisibilityConverter}, ElementName=menuButton}"
              VerticalAlignment="Top"
              Grid.Row="1"
              Background="Wheat">
            <StackPanel x:Name="menuItems">
                <TextBlock Text="simulated...." />
                <TextBlock Text="simulated...." />
                <TextBlock Text="simulated...." />
                <TextBlock Text="simulated...." />
                <TextBlock Text="simulated...." />
                <TextBlock Text="simulated...." />
                <TextBlock Text="simulated...." />
                <TextBlock Text="simulated...." />
                <TextBlock Text="simulated...." />
                <TextBlock Text="simulated...." />
            </StackPanel>
            <StackPanel Name="menuLoading">
                <TextBlock Text="Loading..."
                           FontSize="21" />
                <ProgressBar IsIndeterminate="True"
                             Height="3" />
            </StackPanel>
        </Grid>
    </Grid>
</Window>

视图背后的代码

using System.Threading.Tasks;
using System.Windows;

namespace Question_Answer_WPF_App
{
    public partial class MainWindow : Window
    {
        public MainWindow() => InitializeComponent();

        private Task SimulateLoadingResourcesAsnyc() => Task.Delay(3000);

        private async void MenuButton_Click(object sender, RoutedEventArgs e)
        {
            menuItems.Visibility = Visibility.Collapsed;
            menuLoading.Visibility = Visibility.Visible;

            await SimulateLoadingResourcesAsnyc();

            menuItems.Visibility = Visibility.Visible;
            menuLoading.Visibility = Visibility.Collapsed;
        }
    }
}

enter image description here enter image description here enter image description here