首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用UI滚动我自己的配置

使用UI滚动我自己的配置
EN

Code Review用户
提问于 2014-12-24 16:13:55
回答 4查看 438关注 0票数 14

橡胶鸭传奇继续,因为我发现需要滚动我自己的配置。由于该程序实际上是一个*.dll,并且可供多个主机应用程序使用,所以使用app.config不是一种选择。我决定利用XML序列化来允许用户修改在任务列表中获取的评论。我觉得一切都开始得很好,然后当我构建UI的时候,一切都向南发展。我试图将显示令牌的关注点与令牌的实际想法分开,但不幸地失败了。我还有一个公共静态类,我对它并不满意。我闻到了反模式的味道。

首先,我创建了根Configuration类。

代码语言:javascript
复制
using System;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.InteropServices;

namespace Rubberduck.Config
{
    [ComVisible(false)]
    [XmlTypeAttribute(AnonymousType = true)]
    [XmlRootAttribute(Namespace = "", IsNullable = false)]
    public class Configuration
    {
        public UserSettings UserSettings { get; set; }

        public Configuration()
        {
            //default constructor required for serialization
        }

        public Configuration(UserSettings userSettings)
        {
            this.UserSettings = userSettings;
        }
    }
}

UserSettings也同样是笔直的。

代码语言:javascript
复制
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using System.Runtime.InteropServices;

namespace Rubberduck.Config
{
    [ComVisible(false)]
    [XmlTypeAttribute(AnonymousType = true)]
    public class UserSettings
    {
        public ToDoListSettings ToDoListSettings { get; set; }
        public CodeInspectionSettings CodeInspectinSettings { get; set; }

        public UserSettings()
        {
            //default constructor required for serialization
        }

        public UserSettings(ToDoListSettings todoSettings, CodeInspectionSettings codeInspectionSettings)
        {
            this.ToDoListSettings = todoSettings;
            this.CodeInspectinSettings = codeInspectionSettings;
        }
    }
}

就像TodoListSettings

代码语言:javascript
复制
using System.Xml.Serialization;
using System.Runtime.InteropServices;

namespace Rubberduck.Config
{
    interface IToDoListSettings
    {
        ToDoMarker[] ToDoMarkers { get; set; }
    }

    [ComVisible(false)]
    [XmlTypeAttribute(AnonymousType = true)]
    public class ToDoListSettings : IToDoListSettings
    {
        [XmlArrayItemAttribute("ToDoMarker", IsNullable = false)]
        public ToDoMarker[] ToDoMarkers { get; set; }

        public ToDoListSettings()
        {
            //empty constructor needed for serialization
        }

        public ToDoListSettings(ToDoMarker[] markers)
        {
            this.ToDoMarkers = markers;
        }
    }
}

最后,实际表示字符串标记的类TodoMarker

代码语言:javascript
复制
using System.Xml.Serialization;
using System.Runtime.InteropServices;
using Rubberduck.VBA.Parser;

namespace Rubberduck.Config
{
    [ComVisible(false)]
    public enum TodoPriority
    {
        Low,
        Normal,
        High
    }

    [ComVisible(false)]
    public interface IToDoMarker
    {
        TodoPriority Priority { get; set; }
        string Text { get; set; }
    }

    [ComVisible(false)]
    [XmlTypeAttribute(AnonymousType = true)]
    public class ToDoMarker : IToDoMarker
    {
        //either the code can be properly case, or the XML can be, but the xml attributes must here *exactly* match the xml
        [XmlAttribute]
        public string Text { get; set; }

        [XmlAttribute]
        public TodoPriority Priority { get; set; }

        /// <summary>   Default constructor is required for serialization. DO NOT USE. </summary>
        public ToDoMarker()
        {
            // default constructor required for serialization
        }

        public ToDoMarker(string text, TodoPriority priority)
        {
            Text = text;
            Priority = priority;
        }

        /// <summary>   Convert this object into a string representation. Over-riden for easy databinding.</summary>
        /// <returns>   The Text property. </returns>
        public override string ToString()
        {
            return this.Text;
        }
    }
}

我对这些类的评论是开放的,但它们实际上只是为了上下文。我真正好奇的是我将Configuration加载到UI中以进行操作的方法。我使用静态ConfigurationLoader类将XML文件序列化为Configuration对象,然后将其注入TodoModel中。

代码语言:javascript
复制
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization;
using System.Runtime.InteropServices;
using System.IO;
using Rubberduck.Inspections;
using System.Reflection;
using Rubberduck.VBA.Parser.Grammar;

namespace Rubberduck.Config
{
    [ComVisible(false)]
    public static class ConfigurationLoader
    {
        private static string configFile = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\Rubberduck\rubberduck.config";

        /// <summary>   Saves a Configuration to Rubberduck.config XML file via Serialization.</summary>
        public static void SaveConfiguration<T>(T toSerialize)
        {
            XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());
            using (TextWriter textWriter = new StreamWriter(configFile))
            {
                xmlSerializer.Serialize(textWriter, toSerialize);
            }
        }

        /// <summary>   Loads the configuration from Rubberduck.config xml file. </summary>
        /// <remarks> If an IOException occurs returns a default configuration.</remarks>
        public static Configuration LoadConfiguration()
        {
            try
            {
                using (StreamReader reader = new StreamReader(configFile))
                {
                    var deserializer = new XmlSerializer(typeof(Configuration));
                    return (Configuration)deserializer.Deserialize(reader);
                }
            }
            catch (IOException)
            {
                return GetDefaultConfiguration();
            }
        }

        public static Configuration GetDefaultConfiguration()
        {
            var userSettings = new UserSettings(
                                    new ToDoListSettings(GetDefaultTodoMarkers()),
                                    new CodeInspectionSettings(GetDefaultCodeInspections())
                               );

            return new Configuration(userSettings);
        }

        public static ToDoMarker[] GetDefaultTodoMarkers()
        {
            var note = new ToDoMarker("NOTE:", TodoPriority.Low);
            var todo = new ToDoMarker("TODO:", TodoPriority.Normal);
            var bug = new ToDoMarker("BUG:", TodoPriority.High);

            return new ToDoMarker[] { note, todo, bug };
        }

        // omitted some reflection methods for a different set of configs for brevity
    }
}

TodoModel中,我最初只注入了标记,但结果表明它需要能够将对象序列化为XML。所以,它现在叫public static ConfigurationLoader,我闻到了一股气味。

代码语言:javascript
复制
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Rubberduck.Config;
using System.ComponentModel;

namespace Rubberduck.UI.Settings
{
    public class TodoSettingModel
    {
        private BindingList<ToDoMarker> _markers;
        public BindingList<ToDoMarker> Markers { get { return _markers; } }

        public TodoSettingModel(List<ToDoMarker> markers)
        {
            _markers = new BindingList<ToDoMarker>(markers);
        }

        public void Save()
        {
            var settings = new ToDoListSettings(_markers.ToArray());
            var config = ConfigurationLoader.LoadConfiguration();
            config.UserSettings.ToDoListSettings = settings;

            ConfigurationLoader.SaveConfiguration<Configuration>(config);
        }
    }
}

最后但并非最不重要。下面是我的TodoSettingsControl的代码,它嵌入到表单中。

代码语言:javascript
复制
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Rubberduck.Config;

namespace Rubberduck.UI.Settings
{
    public partial class TodoListSettingsControl : UserControl
    {
        private TodoSettingModel _model;
        private IToDoMarker _activeMarker;

        /// <summary>   Parameterless Constructor is to enable design view only. DO NOT USE. </summary>
        public TodoListSettingsControl()
        {
            InitializeComponent();
        }

        public TodoListSettingsControl(TodoSettingModel model):this()
        {
            _model = model;
            this.tokenListBox.DataSource = _model.Markers;
            this.tokenListBox.SelectedIndex = 0;
            this.priorityComboBox.DataSource = Enum.GetValues(typeof(Config.TodoPriority));

            SetActiveMarker();
        }

        private void SetActiveMarker()
        {
            _activeMarker = (IToDoMarker)this.tokenListBox.SelectedItem;
            if (_activeMarker != null && this.priorityComboBox.Items.Count > 0)
            {
                this.priorityComboBox.SelectedIndex = (int)_activeMarker.Priority;
            }

            this.tokenTextBox.Text = _activeMarker.Text;
        }

        private void tokenListBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            SetActiveMarker();
        }

        private void saveChangesButton_Click(object sender, EventArgs e)
        {
            var index = this.tokenListBox.SelectedIndex;
            _model.Markers[index].Text = tokenTextBox.Text;
            _model.Markers[index].Priority = (TodoPriority)priorityComboBox.SelectedIndex;
            _model.Save();
        }

        private void tokenTextBox_TextChanged(object sender, EventArgs e)
        {
            this.saveChangesButton.Enabled = true;
        }

        private void addButton_Click(object sender, EventArgs e)
        {
            var marker = new ToDoMarker(this.tokenTextBox.Text, (TodoPriority)this.priorityComboBox.SelectedIndex);
            _model.Markers.Add(marker);
            _model.Save();

            this.tokenListBox.DataSource = _model.Markers;
        }

        private void removeButton_Click(object sender, EventArgs e)
        {
            _model.Markers.RemoveAt(this.tokenListBox.SelectedIndex);
            _model.Save();

            this.tokenListBox.DataSource = _model.Markers;
        }

    }
}

最后看起来都是这样。

EN

回答 4

Code Review用户

回答已采纳

发布于 2015-01-02 10:03:18

我认为几个角色接口在这里会很有帮助。您需要对配置做两件事

  • 加载它
  • 省省吧

因此:

代码语言:javascript
复制
public interface IConfigurationReader
{
    Configuration ReadConfiguration();
}

public interface IConfigurationWriter
{
    void WriteConfiguration<T>(T configuration);
}

很明显,这些名字还可以改进。然后,您可以创建一个实现两者的类:

代码语言:javascript
复制
public class XmlConfigurationStore : IConfigurationReader, IConfigurationWriter
{
    // Implementation as before
}

您还可以添加这些内容来获取默认值,但不确定这在接口上是否有意义,或者可能作为ConfigurationStore基类的一部分。

您可能希望将这些服务组合到您的控件中:

代码语言:javascript
复制
public partial class TodoListSettingsControl : UserControl
{
    private readonly IConfigurationReader configurationReader;
    private readonly IConfigurationWriter configurationWriter;

    public TodoListSettingsControl()
    {
        var configStore = new XmlConfigurationStore;
        configurationReader = configurationWriter = configStore;
        // Anything else.
    }
}

我真的不同意视图模型保存自己-它让我想起了活动记录(我从来没有真正喜欢过)。它完成了工作,但我认为如果TodoListSettingsModel类只是一些数据的包装器,并且您有另一个负责保存模型的服务,那么您的代码会工作得更好。这个服务几乎可以肯定地同时依赖于IConfigurationReaderIConfigurationWriter

票数 4
EN

Code Review用户

发布于 2015-01-01 22:13:53

我一般不想再加第二个答案,但我要提出的建议比我之前提议的要大得多,这似乎还不够。

我从GitHub中删除了整个源代码,问题中真正缺少的是如何“注入”这些依赖项。具体来说,您正在表单的构造函数中添加大量的new,然后从那里开始。然后在后面的页面中,在一个表单事件中,有以下内容:

代码语言:javascript
复制
var markers = new List<ToDoMarker>(_config.UserSettings.ToDoListSettings.ToDoMarkers);
controlToActivate = new TodoListSettingsControl(new TodoSettingModel(markers));

有几件事对我来说很突出:

  1. 如果您的模型在表单周围的不同位置连接起来,那么您就没有真正正确地注入依赖关系。如果您想注入依赖项,可以根据您是多大程度的纯粹主义者以及您想要进行更改的程度来做出几个选择:
    1. 订阅“纯依赖项注入”方法,并在Main()中滚动您自己的DI,尝试解析表单本身并将依赖项传递给它们的构造函数(不推荐)。
    2. 通过使用容器作为服务定位器进行折衷,在容器中注册对象图,并尽早在表单(或构造函数中)解决顶级依赖关系。
    3. new表单的构造函数中所需的所有内容,让它充当每个表单的引导程序。

  2. 一般来说,如果要将数据结构传递给的对象也是数据结构,则实际上只需要将数据结构作为依赖项。这样做意味着您必须有一个顶级的协调器,以一种有意义的方式获取数据结构并将其传递进来。最好的方法是将生成或包含数据结构的对象直接传递给对象。在这种情况下,直接将设置传递到模型中。
  3. 为什么对数组如此着迷?如果序列化是必需的,那么就这样吧,但是类型之间需要进行大量的转换。例如,有一个方法将所有的IInspections转换成一个数组,然后对数组进行迭代,以便每个项被用来实例化一个CodeInspection。您可以将内部方法的方法签名更改为只返回一个IEnumerable,并且可以使用.Select()将其投影到一个新集合中,在lambda中定义实例化。然后,如果你必须的话,你可以转换成一个列表。
  4. 在这一点上,为什么要将一个接口传递给一个表面上是相同的具体类?我看到具体的类一直使用到App类。为什么不直接使用这个界面来让自己更容易呢?如果您必须在层上将其转换为具体的类,则界面的好处几乎是否定的。
  5. 您的设置依赖关系太分散了。您可能甚至不需要有三个不同的类,一个类包含另外两个类的实例进行设置。只有一个设置对象与所有属性。如果绝对必须有两个独立的依赖类,那么最顶层的设置对象应该由其依赖项的属性组成,而不是直接公开其依赖项。您应该将两个依赖对象的属性映射到设置对象的属性上,并让设置的使用者处理这些道具。消费者绝对没有必要深入到对象层次结构中,除非在这种情况下有100个properties...which,应该用组成设置的单独的对象层来组织。
  6. 您提到了要将显示令牌的关注点与令牌本身的想法分开。该模型是一个很好的抽象。如果您想更进一步,您可以从MVVM书籍中获取一个页面,并让模型表现得像一个视图模型。模型具有一个属性,该属性定义了当标记集合更改时应该触发的委托事件。表单在实例化模型时将模型设置为一个委托,该委托封装了将控件的数据源分配给标记列表的逻辑。然后,要么模型将集合公开为readonly,并且有自己的方法来修改激发委托的集合,要么您可以保留代码的原样,让模型的save方法调用委托。可能有必要也可能没有必要走到这一步。增加的复杂性也带来了好处--你确实有能力测试更多的逻辑,委托只定义一次,传递到视图模型,然后从那里被遗忘。

如果太长的话,我可以保持简单:合并方法和对象,因为你打破了太多的方法,收紧你的依赖图,让它连接在一个地方,尽可能接近于表单的实例化,并且不要在离UI更近的时候将接口向上转换/转换成具体的类型。

票数 4
EN

Code Review用户

发布于 2014-12-24 17:34:24

嗯,我相信模型是保存配置的正确地方。我认为您关注的原因来自于ConfigurationLoader类的静态特性,我查看了它,没有发现任何设计问题。

促使我提出建议的是如何进行单元测试。你得做个权衡。一方面,如果不必每次执行ConfigurationLoader的SaveConfiguration,就可以更容易地测试模型,方法是更改代码,将其的一个实例与模型构造函数中的标记一起注入,并模拟SaveConfiguration方法。另一方面,ConfigurationLoader的行为更像是一个内部类,如果您不让内部元素对您的测试项目可见,那么您就失去了能够直接在方法上运行测试的粒度。

我的建议是使ConfigurationLoader成为一个内部类,并让您的工作在应用程序的生命周期内执行注入、管理它的一个实例。这对你有用吗?

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/74782

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档