代码之家  ›  专栏  ›  技术社区  ›  rory.ap

使用抽象和依赖注入,如果特定于实现的细节需要在UI中配置呢?

  •  8
  • rory.ap  · 技术社区  · 6 年前

    ClientMatter 班级:

    public class ClientMatter
    {
        public string ClientNumber { get; set; }
        public string MatterNumber { get; set; }
    }
    

    IMatterListLoader

    public interface IMatterListLoader
    {
        IReadOnlyCollection<string> MatterListFileExtensions { get; }
        IReadOnlyCollection<ClientMatter> Load(FileInfo fromFile);
    }
    

    enter image description here

    我希望允许用户在运行时配置列表开始的行号和列号,因此视图可能如下所示:

    enter image description here

    IMatterListLoader

    public sealed class ExcelMatterListLoader : IMatterListLoader
    {
        public uint StartRowNum { get; set; }
        public uint StartColNum { get; set; }
        public IReadOnlyCollection<string> MatterListFileExtensions { get; set; }
    
        public IReadOnlyCollection<ClientMatter> Load(FileInfo fromFile)
        {
            // load using StartRowNum and StartColNum
        }
    }
    

    行号和列号是特定于msexcel实现的实现细节,视图模型对此一无所知。然而,MVVM要求我控制视图模型中的视图属性,所以如果我 要做到这一点,应该是这样的:

    public sealed class MainViewModel
    {
        public string InputFilePath { get; set; }
    
        // These two properties really don't belong
        // here because they're implementation details
        // specific to an MS Excel implementation of IMatterListLoader.
        public uint StartRowNum { get; set; }
        public uint StartColNum { get; set; }
    
        public ICommandExecutor LoadClientMatterListCommand { get; }
    
        public MainViewModel(IMatterListLoader matterListLoader)
        {
            // blah blah
        }
    }
    

    enter image description here

    public sealed class TextFileMatterListLoader : IMatterListLoader
    {
        public bool HasHeaderLine { get; set; }
        public IReadOnlyCollection<string> MatterListFileExtensions { get; set; }
    
        public IReadOnlyCollection<ClientMatter> Load(FileInfo fromFile)
        {
            // load tab-delimited client/matters from each line
            // optionally skipping the header line.
        }
    }
    

    现在我没有msexcel实现所需的行和列编号,但是我有一个布尔标志,指示客户机/事项编号是从第一行(即没有标题行)开始还是从第二行(即有标题行)开始。

    . 如何让视图模型在控制表示关注点的同时仍然保持某些实现细节未知?


    enter image description here

    4 回复  |  直到 6 年前
        1
  •  5
  •   bic    6 年前

    对于要加载的每种类型的文件,都需要一个单独的viewmodel。

    这些viewmodels可以作为依赖项传递给主viewmodel,主viewmodel在需要时调用每个viewmodel上的load;

    public interface ILoaderViewModel
    {
        IReadOnlyCollection<ClientMatter> Load();
    }
    
    public class ExcelMatterListLoaderViewModel : ILoaderViewModel
    {
        private readonly ExcelMatterListLoader loader;
    
        public string InputFilePath { get; set; }
    
        public uint StartRowNum { get; set; }
    
        public uint StartColNum { get; set; }
    
        public ExcelMatterListLoaderViewModel(ExcelMatterListLoader loader)
        {
            this.loader = loader;
        }
    
        IReadOnlyCollection<ClientMatter> Load()
        {
            // Stuff
    
            loader.Load(fromFile);
        }
    }
    
    public sealed class MainViewModel
    {
        private ExcelMatterListLoaderViewModel matterListLoaderViewModel;
    
        public ObservableCollection<ClientMatter> ClientMatters
            = new ObservableCollection<ClientMatter>();
    
        public MainViewModel(ExcelMatterListLoaderViewModel matterListLoaderViewModel)
        {
            this.matterListLoaderViewModel = matterListLoaderViewModel;
        }
    
        public void LoadCommand()
        {
            var clientMatters = matterListLoaderViewModel.Load();
    
            foreach (var matter in clientMatters)
            {
                ClientMatters.Add(matter)
            }
        }
    }
    

    当您向应用程序添加更多类型时,您将创建新的视图模型并将其作为依赖项添加。

        2
  •  0
  •   Joshua VdM    6 年前

    您可以拥有一个函数,该函数基于接口的特定类型构造UI元素。

    public static void ConstructUI(IMatterListLoader loader) {
        Type loaderType = loader.GetType();
        // Do logic based on type
    }
    

    可以为每个IMatterListLoader实现提供类,其中包含有关表示的逻辑。(您不希望将UI表示逻辑与IMatterListLoader实现混合在一起)。

        3
  •  0
  •   Sal    6 年前

    我会加一个 Draw() 方法 IMatterListLoader 接口。您的MainViewModel将调用 绘制() 实际情况如何 IMatterListLoader 将向UI添加所需的任何参数。

    例如,假设您有一个不需要客户机输入的ascimatterlistloader,那么MainViewModel中将不会显示任何内容。但是如果加载了ExcelMatterListLoader,MainViewModel应该添加必要的用户输入。

    public sealed class AsciiMatterListLoader : IMatterListLoader
    {
        public IReadOnlyCollection<string> MatterListFileExtensions { get; set; }
    
        public IReadOnlyCollection<ClientMatter> Load(FileInfo fromFile)
        {
            // load data with no parameters
        }
    
        public Panel Draw()
        {
            // Nothing needs to be drawn
            return null;
        }
    }
    
    public sealed class ExcelMatterListLoader : IMatterListLoader
    {
        public uint StartRowNum { get; set; }
        public uint StartColNum { get; set; }
        public IReadOnlyCollection<string> MatterListFileExtensions { get; set; }
    
        public IReadOnlyCollection<ClientMatter> Load(FileInfo fromFile)
        {
            // load using StartRowNum and StartColNum
        }
    
        public Panel Draw()
        {
            Panel panelForUserParams = new Panel();
            panelForUserParams.Height = 400;
            panelForUserParams.Width = 200;
            TextBox startRowTextBox = new TextBox();
            startRowTextBox.Name = "startRowTextBox";
            TextBox startColumnTextBox = new TextBox();
            startColumnTextBox.Name = "startColumnTextBox";
            panelForUserParams.Children().Add(startRowTextBox);
            panelForUserParams.Children().Add(startColumnTextBox);
            return panelForUserParams;
        }
    }
    
    public sealed class MainViewModel
    {
        public string InputFilePath { get; set; }
        public ICommandExecutor LoadClientMatterListCommand { get; }
    
        public MainViewModel(IMatterListLoader matterListLoader)
        {
            var panel = matterListLoader.Draw();
            if (panel != null)
            {
                    // Your MainViewModel should have a dummy empty panel called "placeHolderPanelForChildPanel"
                    var parent = this.placeHolderPanelForChildPanel.Parent;
                    parent.Children.Remove(this.placeHolderPanelForChildPanel); // Remove the dummy panel
                    parent.Children.Add(panel); // Replace with new panel
            }
        }
    }
    

    您可能需要使用事件处理程序将用户输入更改传递给IMatterListLoader,或者使IMatterListLoader成为UserControl。

    编辑

    @罗里美联社是的,服务层不应该知道UI组件。这里是我调整后的答案,IMatterListLoader只是通过使用字典作为PropertyBag而不是告诉UI绘制什么来公开它所需要的属性。通过这种方式,UI层完成所有UI工作:

    public interface IMatterListLoader
    {
        IReadOnlyCollection<ClientMatter> Load(FileInfo fromFile);
        IDictionary<string, object> Properties { get; }
        void SetProperties(IDictionary<string, object> properties);
    }
    
    public sealed class AsciiMatterListLoader : IMatterListLoader
    {
        public IReadOnlyCollection<string> MatterListFileExtensions { get; set; }
    
        public IDictionary<string, object> Properties
        {
            get 
            {
                return new Dictionary<string, object>(); // Don't need any parameters for ascii files
            }
        }
    
        public void SetProperties(IDictionary<string, object> properties)
        {
            // Nothing to do
        }
    
        public IReadOnlyCollection<ClientMatter> Load(FileInfo fromFile)
        {
            // Load without using any additional params
            return null;
        }
    }
    
    public sealed class ExcelMatterListLoader : IMatterListLoader
    {
        private const string StartRowNumParam = "StartRowNum";
        private const string StartColNumParam = "StartColNum";
    
        public uint StartRowNum { get; set; }
        public uint StartColNum { get; set; }
        public IReadOnlyCollection<string> MatterListFileExtensions { get; set; }
    
        private bool havePropertiesBeenSet = false;
    
        public IDictionary<string, object> Properties
        {
            get
            {
                var properties = new Dictionary<string, object>();
                properties.Add(StartRowNumParam, (uint)0); // Give default UINT value so UI knows what type this property is
                properties.Add(StartColNumParam, (uint)0); // Give default UINT value so UI knows what type this property is
    
                return properties;
            }
        }
    
        public void SetProperties(IDictionary<string, object> properties)
        {
            if (properties != null)
            {
                foreach(var property in properties)
                {
                    switch(property.Key)
                    {
                        case StartRowNumParam:
                            this.StartRowNum = (uint)property.Value;
                            break;
                        case StartColNumParam:
                            this.StartColNum = (uint)property.Value;
                            break;
                        default:
                            break;
                    }
                }
    
                this.havePropertiesBeenSet = true;
            }
            else
                throw new ArgumentNullException("properties");
        }
    
        public IReadOnlyCollection<ClientMatter> Load(FileInfo fromFile)
        {
            if (this.havePropertiesBeenSet)
            {
                // Load using StartRowNum and StartColNum
                return null;
            }
            else
                throw new Exception("Must call SetProperties() before calling Load()");
        }
    }
    
    public sealed class MainViewModel
    {
        public string InputFilePath { get; set; }
        public ICommandExecutor LoadClientMatterListCommand { get; }
        private IMatterListLoader matterListLoader;
    
        public MainViewModel(IMatterListLoader matterListLoader)
        {
            this.matterListLoader = matterListLoader;
    
            if (matterListLoader != null && matterListLoader.Properties != null)
            {
                foreach(var prop in matterListLoader.Properties)
                {
                    if (typeof(prop.Value) == typeof(DateTime))
                    {
                        // Draw DateTime picker for datetime value
                        this.placeHolderPanelForParams.Add(new DateTimePicker() { Name = prop.Key });
                    }
                    else 
                    {
                        // Draw textbox for everything else
                        this.placeHolderPanelForParams.Add(new TextBox() { Name = prop.Key });
    
                        // You can also add validations to the input here (E.g. Dont allow negative numbers of prop is unsigned)
                        // ...
                    }
                }
            }
        }
    
        public void LoadFileButtonClick(object sender, EventArgs e)
        {
            //Get input params from UI
            Dictionary<string, object> properties = new Dictionary<string, object>();
            foreach(Control propertyControl in this.placeHolderPanelForParams().Children())
            {
                if (propertyControl is TextBox)
                    properties.Add(propertyControl.Name, ((TextBox)propertyControl).Text);
                else if (propertyControl is DateTimePicker)
                    properties.Add(propertyControl.Name, ((DateTimePicker)propertyControl).Value);
            }
    
            this.matterListLoader.SetProperties(properties);
            this.matterListLoader.Load(null); //Ready to load
        }
    }
    
        4
  •  -1
  •   X39    6 年前

    创建一个新的 Attribute 例如:

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class ExposeToViewAttribute : Attribute
    {
        public string Name { get; set; }
    
        public ExposeToViewAttribute([System.Runtime.CompilerServices.CallerMemberName]string name = "")
        {
            this.Name = name;
        }
    }
    

    var t = matterListLoader.GetType();
    var props = t.GetProperties().Where((p) => p.GetCustomAttributes(typeof(ExposeToViewAttribute), false).Any());
    foreach(var prop in props)
    {
        var att = prop.GetCustomAttributes(typeof(ExposeToViewAttribute), true).First() as ExposeToViewAttribute;
        //Add to view
    }
    

    方法不会变得更干净。

    使用方法简单如下:

    [ExposeToView]
    public int Something { get; set; }
    
    [ExposeToView("some name")]
    public int OtherFieldWithCustomNameThen { get; set; }
    

    如果你用 WPF

    推荐文章