使用YDock的WPF MVVM RichText演示[面板]

目录

​编辑

介绍

背景

使用代码

MVVM结构/特性

安装MVVM工具包

视图、概念和代码

Mod_Public

MVVM模式——详细信息

名为TextData的模型类

名为TestingViewModel的ViewModel类

名为DocViewModel的ViewModel类

将内容放在一起——WPF概念和代码

QAT(QuickAccess工具栏)

DependencyInjection或ServiceInjection

使用ISaveAsFileDlgVM的保存文件对话框示例

信使

事件触发器

ObservableCollection

RichText

更新到版本1.4

结论

信用/参考资料


介绍

本文和演示是关于如何开始使用YDockCommunityToolkit.Mvvm以及为MessageBox和一些对话框创建的一些自定义接口/服务。

背景

有很多关于其他对接框架的 CodeProject 文章,但没有一篇关于YDock。所以我开始将YDockCommunityToolkit.Mvvm结合在一起。

ModelView  ViewModelMVVM模式)是组织或构建代码的好方法,可帮助您简化、开发和测试(例如,单元测试)应用程序。

模型保存数据,与应用程序逻辑无关。

ViewModel 充当ModelView之间的连接。

视图是用户界面。

我不会描述和解释完整演示项目的每一个细节。重点是如何使用对接框架、MVVM工具包和一些功能。

使用代码

MVVM结构/特性

MVVM工具包来自Microsoft,其他一些使用的功能也不是我自己的:来源如信用/参考资料部分所列。

内容快速概览

  • YDock
  • CommunityToolkit.Mvvm和.NET 4.8
    • RelayCommand
    • OnPropertyChanged
    • ObservableRecipient (MessengerViewModelBase)
    • DependencyInjection(作为服务运行MsgBoxDialog
    • ObservableCollection(对于信用Listbox)
  • Ribbon菜单
  • 服务/对话框
  • 使用ICommand将按钮绑定到ViewModel

安装MVVM工具包

安装MVVM工具包的NuGet包时,它会安装67个其他包。

对于DependencyInjection,我们需要安装另一个NuGet包:

Microsoft.Extensions.DependencyInjection

我为MsgBox和一些对话框制作了接口/服务。

DependencyInjection设法独立于viewmodel启动以下对话框:

  • FontDlgVM
  • MsgBoxService
  • DialogVM
  • OpenFileDlgVM
  • RibbonStatusService
  • SaveAsFileDlgVM
  • SearchDlgVM

这允许使用自定义Messageboxes/对话框以及单元测试。

对接框架的使用

发现优秀YDockYDockTest项目@GitHub

对于可重复使用的样本,我们必须用usercontrols替换一些虚拟documentstoolwindows

我的示例项目称为MyWorksForYDock

MainWindow类背后的代码是:

namespace MyWorksForYDock
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Loaded += MainWindow_Loaded;
            Closing += MainWindow_Closing;

            _Init();
        }

        static string SettingFileName { get { return string.Format(@"{0}\{1}", 
                      Environment.CurrentDirectory, "Layout.xml"); } }

        public DocView doc_new;
        public DocView doc_0;
        private Doc left;
        private Doc right;
        private RibbonView top;
        public LogView bottom;
        private Doc left_1;
        private Doc right_1;
        private CreditsView bottom_1;
        private MainWindow wnd;

        private void _Init()
        {
            doc_new = new DocView("New_doc");
            doc_0 = new DocView("doc_0");
            left = new Doc("left");
            right = new Doc("right");
            top = new RibbonView("Ribbon");
            bottom = new LogView("Log");
            left_1 = new Doc("left_1");
            right_1 = new Doc("right_1");
            bottom_1 = new CreditsView("Credits");

            DockManager.RegisterDocument(doc_0);
            DockManager.RegisterDock(top, DockSide.Top);
            DockManager.RegisterDock(bottom, DockSide.Bottom);
            DockManager.RegisterDock(left, DockSide.Bottom);
            DockManager.RegisterDock(right, DockSide.Right);
            DockManager.RegisterDock(left_1, DockSide.Left);
            DockManager.RegisterDock(right_1, DockSide.Right);
            DockManager.RegisterDock(bottom_1, DockSide.Bottom);
        }

        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            wnd = this;
            if (File.Exists(SettingFileName))
            {
                var layout = XDocument.Parse(File.ReadAllText(SettingFileName));
                foreach (var item in layout.Root.Elements())
                {
                    var name = item.Attribute("Name").Value;
                    if (DockManager.Layouts.ContainsKey(name))
                        DockManager.Layouts[name].Load(item);
                    else DockManager.Layouts[name] = 
                         new YDock.LayoutSetting.LayoutSetting(name, item);
                }

                DockManager.ApplyLayout("MainWindow");
            }
            else
            {
                doc_0.DockControl.Show();
                top.DockControl.Show();
                bottom.DockControl.Show();
                left.DockControl.Show();
                right.DockControl.Show();
                left_1.DockControl.Show();
                right_1.DockControl.Show();
                bottom_1.DockControl.Show();
            }
        }

        private void MainWindow_Closing(object sender, CancelEventArgs e)
        {
            DockManager.SaveCurrentLayout("MainWindow");

            var doc = new XDocument();
            var rootNode = new XElement("Layouts");
            foreach (var layout in DockManager.Layouts.Values)
                layout.Save(rootNode);
            doc.Add(rootNode);

            doc.Save(SettingFileName);

            DockManager.Dispose();
        }

        private void OnClick(object sender, RoutedEventArgs e)
        {
            var item = sender as MenuItem;
            if (item.Header.ToString() == "left")
                left.DockControl.Show();
            if (item.Header.ToString() == "left_1")
                left_1.DockControl.Show();
            if (item.Header.ToString() == "Ribbon (top)")
                top.DockControl.Show();
            if (item.Header.ToString() == "right")
                right.DockControl.Show();
            if (item.Header.ToString() == "right_1")
                right_1.DockControl.Show();
            if (item.Header.ToString() == "Log (bottom)")
                bottom.DockControl.Show();
            if (item.Header.ToString() == "Credits")
                bottom_1.DockControl.Show();
            if (item.Header.ToString() == "doc_0")
                doc_0.DockControl.Show();
            if (item.Header.ToString() == "New_doc" && (doc_new.DockControl is object))
                 if (doc_new.DockControl == null)
                    // TODO
                    System.Windows.MessageBox.Show
                    ("Unexpected error: New_doc n.a. " + Environment.NewLine );
                else if (doc_new.DockControl is object)
                                    doc_new.DockControl.Show();
        }
    }

    public class Doc : TextBlock, IDockSource
    {
        public Doc(string header)
        {
            _header = header;
        }

        private IDockControl _dockControl;
        public IDockControl DockControl
        {
            get
            {
                return _dockControl;
            }

            set
            {
                _dockControl = value;
            }
        }

        private string _header;
        public string Header
        {
            get
            {
                return _header;
            }
        }

        public ImageSource Icon
        {
            get
            {
                return null;
            }
        }
    }
}

视图、概念和代码

MainWindow显示usercontrolRibbonViewCreditsViewDocViewLogView

其他的usercontrol仍然是仿制品。

注册服务/视图模型在App代码隐藏中。

namespace MyWorksForYDock
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        private bool blnReady;

        public App()
        {
            InitializeComponent();
            Exit += (_, __) => OnClosing();
            Startup += Application_Startup;

            try
            {
                Mod_Public.sAppPath = Directory.GetCurrentDirectory();
                Ioc.Default.ConfigureServices(
                    new ServiceCollection()
                    .AddSingleton<IMsgBoxService, MsgBoxService>()
                    .AddSingleton((IDialog)new DialogVM())
                    .AddSingleton((IOpenFileDlgVM)new OpenFileDlgVM())
                    .AddSingleton((ISaveAsFileDlgVM)new SaveAsFileDlgVM())
                    .AddSingleton((IRichTextDlgVM)new RichTextDlgVM())
                    .BuildServiceProvider());
            }
            catch (Exception ex)
            {
                File.AppendAllText(Mod_Public.sAppPath + @"\Log.txt", 
                     string.Format("{0}{1}", Environment.NewLine, 
                     DateAndTime.Now.ToString() + "; " + ex.ToString()));
                var msgBoxService = Ioc.Default.GetService<IMsgBoxService>();
                msgBoxService.Show("Unexpected error:" + Constants.vbNewLine + 
                     Constants.vbNewLine + ex.ToString(), img: MessageBoxImage.Error);
            }
        }

        private void OnClosing()
        {
        }

        private void Application_Startup(object sender, EventArgs e)
        {
            blnReady = true;
        }
    }
}

Mod_Public

Mod_Public包括:

public static void ErrHandler(string sErr)

和:

public static string ReadTextLines(string FileName)

MVVM模式——详细信息

WPF Ribbon的角度来看,数据结构/模型很简单:

Ribbon具有菜单项/功能区按钮,它们与ActiveRichTextBoxActiveTextBox一起工作。

这就是我们在模型TextData中看到的。

名为TextData的模型类

using CommunityToolkit.Mvvm.ComponentModel;
using MyWorksForYDock.Views;
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Controls.Ribbon;

namespace MyWorksForYDock
{

    public class TextData : ObservableRecipient, INotifyPropertyChanged
    {
        private string _text;
        private string _richText;
        private RibbonTextBox _NotifyTest;
        private string _readText;
        private TextBox _ActiveTextBox;

        private RichTextBox _ActiveRichTextBox;
        private Ribbon _MyRibbonWPF;
        private RibbonView _MyMainWindow;

        public TextData()
        {
            // 
        }

        public RibbonView MyMainWindow
        {
            get { return _MyMainWindow; }
            set { _MyMainWindow = value; }
        }

        public Ribbon MyRibbonWPF
        {
            get { return _MyRibbonWPF; }
            set { _MyRibbonWPF = value; }
        }

        public RibbonTextBox NotifyTestbox
        {
            get { return _NotifyTest; }
            set { _NotifyTest = value; }
        }

        public string RichText
        {
            get { return _richText; }
            set 
            {
                _richText = value;
                OnPropertyChanged("RichText");
            }
        }

        public string GetText
        {
            get { return _text; }
            set 
            { _text = value;
                OnPropertyChanged("GetText");
            }
        }

        public string ReadText
        {
            get { return _readText; }
            set
            {
                _readText = value;
                GetText = _readText;
                OnPropertyChanged("ReadText");
            }
        }

        public TextBox ActiveTextBox
        {
            get { return _ActiveTextBox; }
            set
            {
                _ActiveTextBox = value;
                OnPropertyChanged("ActiveTextBox");
            }
        }

        public RichTextBox ActiveRichTextBox
        {
            get { return _ActiveRichTextBox; }
            set {_ActiveRichTextBox = value; }
        }
    }
}

名为TestingViewModelViewModel

调用TestingViewModel的类包含用于测试某些MVVM功能的属性,ICommands和方法。它还包含ObservableCollectionOf Credits)的代码,它用于Listview和本文的参考文献/信用。

名为DocViewModelViewModel

此类用于DocView

将内容放在一起——WPF概念和代码

QATQuickAccess工具栏)

您可以从QAT中删除按钮(右键单击时,会出现一个上下文菜单)。您可以在Ribbon上显示QAT。您也可以从设置选项卡恢复QAT。您可以更改Ribbonbackcolor

DependencyInjectionServiceInjection

如前所述,在应用程序后面的代码中有一些代码。

使用ISaveAsFileDlgVM的保存文件对话框示例

它使用interface ISaveAsFileDlgVMservice/viewmodel SaveAsFileDlgVM

... 
public ICommand SaveAsFileDlgCommand { get; set; } 
... 
    RelayCommand cmdSAFD = new RelayCommand(SaveAsFileDialog); 
    SaveAsFileDlgCommand = cmdSAFD; 
... 
    private void SaveAsFileDialog() 
    {
        var dialog = Ioc.Default.GetService<ISaveAsFileDlgVM>(); 
        if (ActiveRichTextBox is object) 
        { 
            dialog.SaveAsFileDlg(_textData.RichText, ActiveRichTextBox); 
        } 
        if  (ActiveTextBox is object) 
        { 
            dialog.SaveAsFileDlg(_textData.GetText, ActiveTextBox); 
        } 
    } 
...

而且,在RibbonView XAML文件中非常重要的Command="{Binding SaveAsFileDlgCommand}"/>

<RibbonButton x:Name="SaveAs" Content="RibbonButton" HorizontalAlignment="Left" 
         Height="Auto" 
         Margin="94,24,-162,-70" VerticalAlignment="Top" Width="80" 
         Label=" Save As"  KeyTip="S" 
         AutomationProperties.AccessKey="S" AutomationProperties.AcceleratorKey="S" 
             SmallImageSource="/Images/save16.png" 
             CanAddToQuickAccessToolBarDirectly="False" 
             ToolTipTitle="Save As"
         Command="{Binding SaveAsFileDlgCommand, Mode=OneWay, 
             UpdateSourceTrigger=PropertyChanged}"/>

Ribbon中,您可以启动和测试其他对话框,或者使用messagebox

  • 打开对话框
  • 搜索(来源,如Credits/References中所列)
  • OpenFileDialog
  • 选项卡帮助>信息
  • FontDialog

信使

添加Inherits ObservableRecipient是很重要的,这个和其他细节在ObservableObject - Windows Community Toolkit |Microsoft 文档中有描述 

查看特定消息应在已加载事件中注册一个视图并取消注册在已卸载事件中注册,以防止内存泄漏和多次回调注册问题。

我们为CreditsView使用ClassTestingViewModelRibbonViewLogView

但是,即使我们将相同的VM用于不同的视图,我们也会运行不同的VM实例,并且必须从Class TestingViewModel发送Msg进行数据交换:

using CommunityToolkit.Mvvm.Messaging;

namespace MyWorksForYDock
{ public class OpenFileDlgVM : ObservableRecipient, IOpenFileDlgVM
    {
        private string msg;
        private RichTextBox myRTB;
        private TextBox myTB;

        public OpenFileDlgVM()
        {
            // 
        }

        public string OpenFileDlg(object ActiveTBox)
        {
            FileDialog dialog = new OpenFileDialog();
            try
            {
                dialog.Filter = "All Files(*.*)|*.*|RTF Files (*.rtf)|*.rtf";
                dialog.FilterIndex = 1;
                dialog.Title = "RTE - Open File";
                dialog.DefaultExt = "rtf";
                // dialog.Filter = "Rich Text Files|*.rtf|" &
                // "Text Files|*.txt|HTML Files|" &
                // "*.htm|All Files|*.*"
                dialog.ShowDialog();
                if (string.IsNullOrEmpty(dialog.FileName))
                    return default;
                string strExt;
                strExt = System.IO.Path.GetExtension(dialog.FileName);
                strExt = strExt.ToUpper();

                string FileName = Path.GetFileName(dialog.FileName);
                SetStatus("TestingViewModel", FileName);

                if (ActiveTBox.GetType().ToString() == 
                   "System.Windows.Controls.RichTextBox") // IsNot Nothing Then
                {
                    myRTB = (RichTextBox)ActiveTBox;

                    switch (strExt ?? "")
                    {
                        case ".RTF":
                            {
                                var t = new TextRange
                                        ((TextPointer)myRTB.Document.ContentStart, 
                                        (TextPointer)myRTB.Document.ContentEnd);
                                var file = new FileStream
                                           (dialog.FileName, FileMode.Open);
                                t.Load(file, DataFormats.Rtf);
                                file.Close();
                                break;
                            }

                        default:
                            {
                                var t = new TextRange((TextPointer)
                                        myRTB.Document.ContentStart, 
                                        (TextPointer)myRTB.Document.ContentEnd);
                                var file = new FileStream
                                           (dialog.FileName, FileMode.Open);
                                t.Load(file, DataFormats.Text);
                                file.Close();
                                break;
                            }
                    }
                    string currentFile = dialog.FileName;
                    dialog.Title = "Editor: " + currentFile.ToString();
                }
                else if (ActiveTBox.GetType().ToString() == 
                                    "System.Windows.Controls.TextBox")
                {
                    myTB = (TextBox)ActiveTBox;
                    string currentFile = dialog.FileName;
                    myTB.Text = Mod_Public.ReadTextLines(dialog.FileName);
                }
            }
            catch (Exception ex)
            {
                File.AppendAllText(Mod_Public.sAppPath + @"\Log.txt", 
                    string.Format("{0}{1}", Environment.NewLine, 
                    DateAndTime.Now.ToString() + "; " + ex.ToString()));
                var MsgCmd = new RelayCommand<string>
                             (m => MessageBox.Show("Unexpected error:" + 
                              Constants.vbNewLine + 
                              Constants.vbNewLine + ex.ToString()));
                MsgCmd.Execute("");
            }

            return default;
        }

        public void SetStatus(string r, string m)
        {
            try
            {
                var s = Messenger.Send(new StatusMessage(m));
            }
            catch (Exception ex)
            {
                SetStatus("OpenFileDlgVM", ex.ToString());
                Mod_Public.ErrHandler(ex.ToString());
            }
        }

仅当消息已注册时,才可以发送Msg

using Microsoft.Toolkit.Mvvm.Messaging;
...
     Messenger.Register<DialogMessage>(this, (r, m) => DialogMessage = m.NewStatus);
     Messenger.Register<StatusMessage>(this, (r, m) => StatusBarMessage = m.NewStatus);
     Messenger.Register<PassActiveRTBoxMsg>(this, 
               (r, m) => PassActiveRTBoxMsg = m.ActiveRTBox);
     Messenger.Register<GetCreditsMsg>(this, (r, m) => GetCreditsMsg = m.AddCredit);
...
    ~TestingViewModel()
        {
        Messenger.Unregister<StatusMessage>(this);
        Messenger.Unregister<DialogMessage>(this);
        Messenger.Unregister<PassActiveRTBoxMsg>(this);
        Messenger.Unregister<GetCreditsMsg>(this);
        }

关闭viewmodel时,我们必须注销该消息。

消息出现在StatusBarRibbon上。

事件触发器

要求:Microsoft.Xaml.Behaviors.WpfNuGet 包)

<UserControl
   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:MyWorksForYDock.Views" 
   xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
   xmlns:MyWorksForYDock="clr-namespace:MyWorksForYDock" 
   x:Class="MyWorksForYDock.Views.DocView"
   mc:Ignorable="d" 
   d:DesignHeight="450" d:DesignWidth="800">
   
<Grid>
    <RichTextBox x:Name="myRichTextBox" HorizontalScrollBarVisibility="Auto" 
                 VerticalScrollBarVisibility="Auto" 
                 Margin="2,2,2,0" IsDocumentEnabled="True" AcceptsTab="True">
        <RichTextBox.DataContext>
            <MyWorksForYDock:DocViewModel/>
        </RichTextBox.DataContext>
        <FlowDocument>
             <Paragraph>
                <Run Text="RichTextBox"/>
             </Paragraph>
        </FlowDocument>
        <b:Interaction.Triggers>
            <b:EventTrigger EventName= "MouseWheel">
                <b:InvokeCommandAction Command=
                                "{Binding ParameterisedCommand, Mode=OneWay}" 
                                  CommandParameter="{Binding ElementName=myRichTextBox, 
                                  Mode=OneWay}"/>
            </b:EventTrigger>
            <b:EventTrigger EventName= "MouseDoubleClick">
                <b:InvokeCommandAction Command=
                                "{Binding ParameterisedCommand, Mode=OneWay}" 
                                  CommandParameter="{Binding ElementName=myRichTextBox,
                                  Mode=OneWay}"/>
            </b:EventTrigger>
            <b:EventTrigger EventName= "TextChanged">
                <b:InvokeCommandAction Command=
                                "{Binding ParameterisedCommand, Mode=OneWay}" 
                                  CommandParameter="{Binding ElementName=myRichTextBox,
                                  Mode=OneWay}"/>
            </b:EventTrigger>
                <b:EventTrigger EventName= "MouseEnter">
                    <b:InvokeCommandAction Command=
                                "{Binding ParameterisedCommand, Mode=OneWay}" 
                                  CommandParameter="{Binding ElementName=myRichTextBox,
                                  Mode=OneWay}"/>
            </b:EventTrigger>
        </b:Interaction.Triggers>
    </RichTextBox>

</Grid>
</UserControl>

这用于获取ActiveRichTextBox

ObservableCollection

它是viewmodel TestingViewModel的一部分,用于带有Credits / Referenceslistbox

public class TestingViewModel : ObservableRecipient, INotifyPropertyChanged

#Region " fields"
...
private ObservableCollection<Credits> _credit = new ObservableCollection<Credits>();
...
#End Region
...
    _credit = new ObservableCollection<Credits>()
    {
       new Credits()
           {
              Item = "MVVM Community Toolkit",
              Note = "Microsoft",
              Link = "https://docs.microsoft.com/en-us/windows/
                      communitytoolkit/mvvm/introduction"
           },
       new Credits()
           {
              Item = "MVVMLight",
              Note = "GalaSoft",
              Link = "https://www.codeproject.com/Articles/768427/The-big-MVVM-Template"
           },
       new Credits()
           {
              Item = "YDock",
              Note = "GitHub",
              Link = "https://github.com/yzylovepmn/YDock"
           },
       new Credits()
           {
              Item = "ICommand with MVVM pattern",
              Note = "CPOL",
              Link = "https://www.codeproject.com/Articles/863671/
                      Using-ICommand-with-MVVM-pattern"
           },
       new Credits()
           {
              Item = "C# WPF WYSIWYG HTML Editor - CodeProject",
              Note = "CPOL",
              Link = "https://www.codeproject.com/Tips/870549/
                      Csharp-WPF-WYSIWYG-HTML-Editor"
           },
       new Credits()
           {
              Item = "SearchDialog",
              Note = "Forum Msg",
              Link = "https://social.msdn.microsoft.com/forums/vstudio/en-US/
                      fc46affc-9dc9-4a8f-b845-89a024b263bc/
                      how-to-find-and-replace-words-in-wpf-richtextbox"
           },
       new Credits()
           {
              Item = "Find/ReplaceDialog",
              Note = "Forum Msg",
              Link = "https://www.itcodar.com/csharp/
                      changing-font-for-richtextbox-without-losing-formatting.html"
           }
       };
...
public class Credits
    {
        public string Item { get; set; }
        public string Note { get; set; }
        public string Link { get; set; }
    }

ObservableCollection的优点是,我们的Listbox不需要UpdateTrigger

RichText

从选项卡'RichText'中,您可以选择其RichTextBox中的一些文本并使用RibbonButton来格式化它。其中许多是EditingCommand,并且仅显示在.xaml文件中。

更新到版本1.4

重新设计了所有 ViewModel 类:

在许多情况下使用RelayCommand代替ICommandParamCommand使用。

清理了代码并删除了大部分已禁用的代码段。

结论

这只是一个演示应用程序——它还没有准备好生产。

但我认为YDock框架和MVVM工具包将允许你进行各种扩展。

最后说明:我对任何形式的反馈都非常感兴趣——问题、建议和其他。

信用/参考资料

https://www.codeproject.com/Articles/5355820/WPF-MVVM-RichText-Demo-using-YDock-Panel

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值