Может ли кто-нибудь предоставить конкретный пример WPF «визуальное наследование» для диалогового окна? - PullRequest
0 голосов
/ 09 октября 2019

Я опытный разработчик WinForms, относительно новичок в WPF. У меня есть большое приложение WinForms, которое использует несколько разных базовых классов для представления диалоговых окон. Одним из таких примеров является AbstractOkCancelDialog. Этот класс содержит панель в нижней части диалогового окна с кнопками «ОК» и «Отмена» в правой части панели. Я пытаюсь определить лучший способ справиться с этим, поскольку я понимаю, что WPF не обеспечивает визуальное наследование.

Мне не нужно создавать кнопки «ОК» и «Отмена» и размещать их длякаждый диалог в приложении.

Я прочитал, что способ сделать это в WPF с помощью пользовательских элементов управления. Я могу представить создание пользовательского элемента управления с кнопками ОК и Отмена. Но я не хочу вручную размещать этот пользовательский элемент управления в сотнях диалогов в моем приложении. Мне бы очень хотелось, чтобы было что-то вроде этого:

public AbstractOkCancelDialog = class(Window)
{
  protected AbstractOkCancelDialogViewModel _ViewModel;

  // AbstractOkCancelDialogViewModel would have commands for OK and Cancel.
  // Every dialog would inherit from AbstractOkCancelDialog, and would use
  // a viewmodel that inherits from AbstractOkCancelDialogViewModel. In 
  // this way, all view models would automatically be connected to the OK
  // and Cancel commands.
}

Я видел в Интернете некоторые дискуссии о том, как создать базовый класс. Эти обсуждения объясняют, как не может быть файла xaml, связанного с базовым классом диалога, и я понимаю это ограничение. Я просто не могу понять, как автоматически разместить пользовательский элемент управления с помощью кнопок OK и Отмена.

Я надеюсь, что кто-то может подсказать мне пример решения, демонстрирующего такую ​​структуру. Заранее спасибо!

Ответы [ 3 ]

0 голосов
/ 09 октября 2019

Вот пример того, как использовать пользовательский AlertDialog
UserControl

<UserControl x:Class="Library.Views.AlertMessageDialogView"
             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:p="clr-namespace:Library.Properties" 
             DataContext="{Binding RelativeSource={RelativeSource Self}}"       
             FlowDirection = "{Binding WindowFlowDirection, Mode=TwoWay}">    

    <Grid Background="{DynamicResource WindowBackgroundBrush}">
        <Canvas HorizontalAlignment="Left" Height="145" VerticalAlignment="Top" Width="385">
            <Label HorizontalAlignment="Left" Height="57" VerticalAlignment="Top" Width="365" Canvas.Left="10" Canvas.Top="10" FontSize="14" >
                <TextBlock x:Name="txtVocabAnglais" TextWrapping="Wrap" Text="{Binding Message, Mode=TwoWay}" Width="365" Height="57"  />
            </Label>
            <Button x:Name="cmdCancel" Content="{x:Static p:Resources.AlertMessageDialogViewcmdCancel}" Height="30" Canvas.Left="163" Canvas.Top="72" Width="71" Command = "{Binding CancelCommand}" CommandParameter = "null" IsDefault="True"/>
        </Canvas>
    </Grid>

</UserControl>

Класс ViewModel

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Windows;
using System.ComponentModel;
using System.Windows.Controls;

namespace Library.ViewModel
{
    public class AlertMessageDialogViewModel : BindableBaseViewModel
    {
        public event EventHandler CloseWindowEvent;

        private string _title;
        private string _message;

        public BaseCommand<string> YesCommand { get; private set; }
        public BaseCommand<string> CancelCommand { get; private set; }
        private WinformsNameSpace.FlowDirection _windowFlowDirection;

        public AlertMessageDialogViewModel()
        {
            CancelCommand = new BaseCommand<string>(cmdCancelBtnClick);
            WindowFlowDirection = CustomFuncVar.WindowFlowDirection;
        }

        public WinformsNameSpace.FlowDirection WindowFlowDirection
        {
            get
            {
                return _windowFlowDirection;
            }
            set
            {
                _windowFlowDirection = value;
                OnPropertyChanged("WindowFlowDirection");
            }
        }

        public string Message
        {
            get
            {
                return _message;
            }
            set
            {
                _message = value;
                OnPropertyChanged("Message");
            }
        }

        public string Title
        {
            get
            {
                return _title;
            }

            set
            {
                _title = value;
            }
        }

        private void cmdCancelBtnClick(string paramerter)
        {
            if (CloseWindowEvent != null)
                CloseWindowEvent(this, null);
        }

    }
}

DialogMessage Class

using System;
using System.Windows;
using System.Collections.Generic;

namespace Library.Helpers
{
    public static class DialogMessage 
    {
        public static void AlertMessage(string message, string title, Window OwnerWindowView)
        {
            try
            {
                //Affichage de méssage de succès enregistrement
                AlertMessageDialogViewModel alertDialogVM = new AlertMessageDialogViewModel();
                alertDialogVM.Message = message;
                alertDialogVM.Title = title;

                // Auto Generation Window
                FrameworkElement view = LpgetCustomUI.AutoGetViewFromName("AlertMessageDialogView");
                view.DataContext = alertDialogVM;
                Dictionary<string, object> localVarWindowProperty = new Dictionary<string, object>();
                localVarWindowProperty = LpgetCustomUI.GetWindowPropretyType_400x145(Properties.Resources.ApplicationTitle);
                CummonUIWindowContainer alertDialogView = new CummonUIWindowContainer(view, null, false, localVarWindowProperty);
                //End Auto Generation Window

                // Attachement de l'évènement de fermture de View au modèle
                alertDialogVM.CloseWindowEvent += new EventHandler(alertDialogView.fnCloseWindowEvent);

                if (OwnerWindowView!=null)
                {
                    alertDialogView.Owner = OwnerWindowView;
                }
                else
                {
                    alertDialogView.WindowStartupLocation = WindowStartupLocation.CenterScreen;
                }

                alertDialogView.ShowDialog();
            }
            catch (Exception ex)
            {

            }
        }
    } 
}


CummonUIWindowContainer Class

namespace CummonUILibrary.CummonUIHelpers
{
    public class CummonUIWindowContainer : Window
    {
        public event RoutedEventHandler CmbRootEvtLanguageChange;
        private FrameworkElement currentView;
        private ContentControl _contentcontainer;

        public CummonUIWindowContainer(string usercontrolName)
        {
            Contentcontainer = new ContentControl();
            currentView = new FrameworkElement();
        }

        public CummonUIWindowContainer()
        {
            Contentcontainer = new ContentControl();
            currentView = new FrameworkElement();
        }

        public CummonUIWindowContainer(FrameworkElement view, object model, bool setDataContextToView, Dictionary<string, object> WindowPropertyList)
        {
            Contentcontainer = new ContentControl();
            Contentcontainer.Name = "ContentControl";

            SetWindowProperty(view, model, setDataContextToView, WindowPropertyList);
        }

        public void SetWindowProperty(FrameworkElement view, object model, bool setDataContextToView, Dictionary<string, object> WindowPropertyList)
        {
            try
            {

                LinearGradientBrush brush = new LinearGradientBrush();
                GradientStop gradientStop1 = new GradientStop();
                gradientStop1.Offset = 0;
                gradientStop1.Color = Colors.Yellow;
                brush.GradientStops.Add(gradientStop1);

                GradientStop gradientStop2 = new GradientStop();
                gradientStop2.Offset = 0.5;
                gradientStop2.Color = Colors.Indigo;
                brush.GradientStops.Add(gradientStop2);
                GradientStop gradientStop3 = new GradientStop();
                gradientStop3.Offset = 1;
                gradientStop3.Color = Colors.Yellow;
                brush.GradientStops.Add(gradientStop3);
                this.Background = brush;

                CurrentView = view;
                Type elementType = this.GetType();
                ICollection<string> WindowPropertyListNames = WindowPropertyList.Keys;

                foreach (string propertyName in WindowPropertyListNames)
                {
                    PropertyInfo property = elementType.GetProperty(propertyName);
                    property.SetValue(this, WindowPropertyList[propertyName]);                    
                }

                if (setDataContextToView == true & model != null)
                {
                    CurrentView.DataContext = model;
                }
                if (CurrentView != null)
                {
                    Contentcontainer.Content = CurrentView;
                }

                //Contentcontainer.Margin = new Thickness(0,0, 0, 0);
                IAddChild container=this;
                container.AddChild(Contentcontainer);                

            }
            catch (Exception ex)
            {
            }
        }

        public void fnCloseWindowEvent(object sender, EventArgs e)
        {
            this.Close();
        }

        public ContentControl Contentcontainer
        {
            get
            {
                return _contentcontainer;
            }
            set
            {
                _contentcontainer = value;
            }
        }

        public FrameworkElement CurrentView
        {
            get
            {
                return currentView;
            }
            set
            {
                if (this.currentView != value)
                {
                    currentView = value;
                    //RaisePropertyChanged("CurrentView");
                }
            }
        }   

        private void cmbLanguage_SelectionChanged(object sender, RoutedEventArgs e)
        {
            //CmbRootEvtLanguageChange(sender, e);
        }

    }
}

Как использовать класс

DialogMessage.AlertMessage("My Custom Message", "My Custom Title Message");
0 голосов
/ 10 октября 2019

Вот как бы я это сделал

Создайте абстрактный базовый класс для своего диалога и измените соответствующий ControlTemplate

AbstractOkCancelDialog

public abstract class AbstractOkCancelDialog : Window
{
    public static readonly DependencyProperty CancelCommandParameterProperty =
        DependencyProperty.Register(
            "CancelCommandParameter",
            typeof(object),
            typeof(AbstractOkCancelDialog),
            new FrameworkPropertyMetadata((object) null));

    public static readonly DependencyProperty CancelCommandProperty =
        DependencyProperty.Register(
            "CancelCommand",
            typeof(ICommand),
            typeof(AbstractOkCancelDialog),
            new FrameworkPropertyMetadata((ICommand) null));

    public static readonly DependencyProperty OkCommandParameterProperty =
        DependencyProperty.Register(
            "OkCommandParameter",
            typeof(object),
            typeof(AbstractOkCancelDialog),
            new FrameworkPropertyMetadata((object) null));

    public static readonly DependencyProperty OkCommandProperty =
        DependencyProperty.Register(
            "OkCommand",
            typeof(ICommand),
            typeof(AbstractOkCancelDialog),
            new FrameworkPropertyMetadata((ICommand) null));


    static AbstractOkCancelDialog()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(AbstractOkCancelDialog), new
            FrameworkPropertyMetadata(typeof(AbstractOkCancelDialog)));
    }

    public ICommand CancelCommand
    {
        get => (ICommand) GetValue(CancelCommandProperty);
        set => SetValue(CancelCommandProperty, value);
    }

    public object CancelCommandParameter
    {
        get => GetValue(CancelCommandParameterProperty);
        set => SetValue(CancelCommandParameterProperty, value);
    }

    public ICommand OkCommand
    {
        get => (ICommand) GetValue(OkCommandProperty);
        set => SetValue(OkCommandProperty, value);
    }

    public object OkCommandParameter
    {
        get => GetValue(OkCommandParameterProperty);
        set => SetValue(OkCommandParameterProperty, value);
    }
}

Стиль

Введите Generic.xaml [?]

<Style
    BasedOn="{StaticResource {x:Type Window}}"
    TargetType="{x:Type local:AbstractOkCancelDialog}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:AbstractOkCancelDialog}">
                <Border
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}">
                    <AdornerDecorator>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="*" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            <ContentPresenter />
                            <Grid Grid.Row="1">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*" />
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="Auto" />
                                </Grid.ColumnDefinitions>
                                <Button
                                    Grid.Column="1"
                                    Margin="5"
                                    Command="{TemplateBinding OkCommand}"
                                    CommandParameter="{TemplateBinding OkCommandParameter}"
                                    Content="Ok"
                                    DockPanel.Dock="Right" />
                                <Button
                                    Grid.Column="2"
                                    Margin="5"
                                    Command="{TemplateBinding CancelCommand}"
                                    CommandParameter="{TemplateBinding CancelCommandParameter}"
                                    Content="Cancel"
                                    DockPanel.Dock="Right" />
                            </Grid>
                        </Grid>
                    </AdornerDecorator>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Теперь вы можете создать свой индивидуальныйдиалоговое окно, подобное созданию любого другого окна

Краткий пример:

TestDialog.xaml

<local:AbstractOkCancelDialog
    x:Class="WpfApp.TestDialog"
    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:local="clr-namespace:WpfApp"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="TestDialog"
    Width="800"
    Height="450"
    OkCommand="{x:Static local:Commands.OkWindowCommand}"
    OkCommandParameter="{Binding RelativeSource={RelativeSource Self}}"
    CancelCommand="{x:Static local:Commands.CancelWindowCommand}"
    CancelCommandParameter="{Binding RelativeSource={RelativeSource Self}}"
    mc:Ignorable="d">
    <Grid>
        <!-- Content -->
    </Grid>
</local:AbstractOkCancelDialog>

TestDialog.xaml.cs

public partial class TestDialog : AbstractOkCancelDialog
{
    ...
}
0 голосов
/ 09 октября 2019

Написать один класс диалога. Это подкласс Window. Он имеет XAML:

<Window
    ...blah blah blah...
    Title="{Binding Title}"
    >
    <StackPanel MinWidth="300">
        <!--  This is how you place content within content in WPF -->
        <ContentControl
            Content="{Binding}"
            Margin="2"
            />
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="2,20,2,2">
            <Button 
                Margin="2" 
                MinWidth="60" 
                DockPanel.Dock="Right" 
                Content="OK" 
                Click="OK_Click" 
                IsDefault="True" 
                />
            <Button 
                Margin="2" 
                MinWidth="60" 
                DockPanel.Dock="Right" 
                Content="Cancel" 
                IsCancel="True" 
                Click="Cancel_Click" 
                />
        </StackPanel>
    </StackPanel>
</Window>

Вы можете придумать это бесконечно, но это достойный минимум, чтобы дать вам произвольный контент над рядом выровненных по правому краю кнопок. Добавление дополнительных кнопок по мере необходимости может включать либо создание шаблонов для этой части окна, либо создание их с помощью ItemsControl (я сделал это в нашем рабочем коде) или несколько других опций.

Использование:

var vm = new SomeDialogViewModel();
var dlg = new MyDialog { DataContext = vm };

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

Я бы предложил написать диалоговый интерфейс модели представления, который, как ожидается, будет реализован потребителем.

public interface IDialogViewModel
{
    String Title { get; set; }
    void OnOK();

    //  Let them "cancel the cancel" if they like. 
    bool OnCancel();
}

Окно может проверить, реализует ли его DataContext этот интерфейс, и действовать соответствующим образом. Если хотите, он может потребовать , чтобы интерфейс и выбрасывать исключение не было реализовано, или он мог бы просто общаться с ним, только если он там есть. Если они не реализуют его, но у них все еще есть свойство Title, привязка к Title все равно будет работать. Привязки "утки".

Естественно, вы можете написать OKCancelDialogViewModel или SelectStringFromListViewModel, написать соответствующие DataTemplates, которые реализуют свои пользовательские интерфейсы, и написать хорошие чистые статические методы, которые показывают их:

public static class Dialogs
{
    public static TOption Select<TOption>(IEnumerable<TOption> options, string prompt,
        string title = "Select Option") where TOption : class
    {
        //  Viewmodel isn't generic because that breaks implicit datatemplating.
        //  That's OK because XAML uses duck typing anyhow. 
        var vm = new SelectOptionDialogViewModel
        {
            Title = title,
            Prompt = prompt,
            Options = options
        };

        if ((bool)new Dialog { DataContext = vm }.ShowDialog())
        {
            return vm.SelectedOption as TOption;
        }

        return null;
    }

    // We have to call the value-type overload by a different name because overloads can't be 
    // distinguished when the only distinction is a type constraint. 
    public static TOption? SelectValue<TOption>(IEnumerable<TOption> options, string prompt,
        string title = "Select Option") where TOption : struct
    {
        var vm = new SelectOptionDialogViewModel
        {
            Title = title,
            Prompt = prompt,
            //  Need to box these explicitly
            Options = options.Select(opt => (object)opt)
        };

        if ((bool)new Dialog { DataContext = vm }.ShowDialog())
        {
            return (TOption)vm.SelectedOption;
        }

        return null;
    }
}

Воттаблица данных модели представления для вышеприведенного диалогового окна выбора:

<Application.Resources>
    <DataTemplate DataType="{x:Type local:SelectOptionDialogViewModel}">
        <StackPanel>
            <TextBlock
                TextWrapping="WrapWithOverflow"
                Text="{Binding Prompt}"
                />
            <ListBox
                ItemsSource="{Binding Options}"
                SelectedItem="{Binding SelectedOption}"
                MouseDoubleClick="ListBox_MouseDoubleClick"
                />
        </StackPanel>
    </DataTemplate>
</Application.Resources>

App.xaml.cs

private void ListBox_MouseDoubleClick(object sender, 
    System.Windows.Input.MouseButtonEventArgs e)
{
    ((sender as FrameworkElement).DataContext as IDialogViewModel).DialogResult = true;
}
var a = Dialogs.Select(new String[] { "Bob", "Fred", "Ginger", "Mary Anne" }, 
            "Select a dance partner:");
var b = Dialogs.SelectValue(Enum.GetValues(typeof(Options)).Cast<Options>(), 
            "Select an enum value:");
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...