Правильный способ обновления свойства, чтобы загрузка не остановила интерфейс? - PullRequest
0 голосов
/ 14 декабря 2018

Извините за заголовок, но это было лучшее, что я мог придумать.Позвольте мне объяснить мою проблему: у меня есть приложение WPF, которое имеет меню, похожее на ваше стандартное верхнее меню, которое занимает всего 5% экрана.Нажмите пункт меню, и вид ниже изменится.Элемент управления меню самодельный по причинам: динамические изменения элементов меню, странный пользовательский интерфейс, который не соответствует существующим элементам управления и т. Д.

Часть представления изменения меню выполняется с помощью ContentControl, который привязывается к элементу управления.Свойство CurrentMenuView.При щелчке по пункту меню происходит следующее (псевдокод):

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 голосов
/ 14 декабря 2018

Вот пример, который имитирует 3 секунды загрузки ... Это не очень сложный пользовательский интерфейс, и привязка к сложному DataTemplates, особенно вложенному, может немного замедлить процесс, но обычно перетаскивание происходит из-за извлечения данных.

Хитрость в том, что у него есть умный пользовательский интерфейс, который заставляет вещи двигаться, но также позволяет пользователю знать, что он ожидает чего-то другого, чтобы продолжить.Например, печально известная полоса загрузки ... Не то чтобы я использовал именно этот процесс, но это правильная идея.

Примечание и отказ от ответственности: я почти презираю код, если только он не находится в пользовательском элементе управления какого-либо типа;никогда в поле зрения.Я всегда предпочитаю MVVM и использую ViewModel для привязки;но не для создания или управления пользовательским интерфейсом только для данных, которые пользовательский интерфейс использует.Все, что сказано, потому что этот пример не из этого.Я просто создал элементы управления для представления и оставил код для ответа на этот вопрос, например, как можно более простым.

Представление

<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

...