如何创建用于定义对话框的流畅界面?

时间:2009-08-07 15:38:08

标签: wpf winforms user-interface fluent-interface

我正在寻找使用流畅界面来定义简单对话框(和其他UI元素)的示例体验

(我可能需要在内部编程语言中添加对自定义对话框的支持,我认为流畅的界面可能是最好的方式)

如果影响你的答案,UI系统将建立在Winforms OR WPF上。


如果界面不流畅怎么办?我将问题改为“简单易用(和读取)API ......”,这不依赖于“拖放”UI设计师的使用。

我认为结果会在某种程度上流利,例如

  

文本框(“名称”)。标记(“人   名称”)。柱(1)

     

文本框(“笔记”)。标记(“注意事项”)。   多行(4)。柱(1).ToColumn(3)

然而,界面不必是单行


这个“How to make Databinding type safe and support refactoring”  为数据绑定的流畅界面提供了一个很好的起点。

6 个答案:

答案 0 :(得分:3)

我为我的对话框构建了一个流畅的界面,类似于:

var result = Dialog
               .Buttons(buttons.Ok, buttons.Cancel)
               .Title("")
               .Text("")
               .Show();

if ( result == DialogResult.Ok) {
    //...
}

我还有一个用于输入这样的枚举:

var result = Dialog(of EnumName)
               .Text("")
               .Title("")
               .Show();

if ( result == EnumName.Value1 ) {
  //...
}

从enum生成按钮,并返回所选按钮的枚举值。

编辑:从评论中添加:

它显示的表单的宽度经过计算,可以适合一行中的所有按钮。 它有一个添加额外控件的方法。 布局由流布局面板制成(一个水平用于按钮。一个垂直用于文本和其他控件) 总体布局是标准消息框。 它还有另一个自动加速按钮的选项。

方法摘要:

.Buttons(paramarray of DialogResult)
.FromEnum<T>(enum)
.Title(text)
.Text(text)
.Control(control)
.AutoAccelerate
.Icon(image)
.Show() as T

答案 1 :(得分:2)

这个问题让我疯狂了好几天。我想你可能需要问的一个问题是“我为什么要为对话框制作一个流畅的API?”

当您查看流行的流畅API时,您会注意到与它们相同的一些内容,因为它有助于用户能够流畅地阅读一行代码。几乎像一句话。观察:

来自Ninject:

Bind(typeof(IWeapon)).To(typeof(Sword));

来自Moq:

mock.Setup(foo => foo.Execute("ping"))
    .Returns(() => calls)
    .Callback(() => calls++);

来自所有流利API的母亲,Linq:

var query = Products
    .Where(p => p.Name.Contains("foo")
    .OrderBy(p => p.Name);

这些是很好的API,几乎可以为它们的使用提供句子结构。

另一个例子是,这是怎么回事:

Dialog.Buttons(buttons.Ok, buttons.Cancel).Title("").Text("")

更具可读性和实用性
new Dialog()
{
     Buttons = Buttons.OkCancel,
     Title = "",
     Text = ""
};

这只是一个简单的例子。我注意到你在一行代码中询问如何填充布局等内容。我的天啊,你的台词会很长。

我认为你需要决定你是否真的认为一个流畅的API在这里获得了一些东西。我所看到的只是在对话框中设置属性而不提供任何可读性或值的方法。

答案 2 :(得分:2)

到目前为止给出的例子并没有减少任务的复杂性;他们只交换一种语法用于另一种语法(几乎同样冗长)。如果你花时间来创建一个流畅的界面,利用它来实际提高你的API的表现力,而不是只是咯咯的语法糖。提升从默认原语(按钮,模态,...)到模板,可视继承链和行为的抽象级别。

我还没有完全考虑过这个问题,但是还有以下几点:

Dialog
 .WithStandardColors()
 .WithTitleOf("ChooseSomething")
 .WithButtonSet<OkCancel>()
 .Show();

Dialog
 .UseErrorFormatting
 .SetTitleTo("Uh Oh")
 .Show()

答案 3 :(得分:1)

流畅界面的LINQ示例:

var customerTurnover = allOrders
                       .Where (o.CustomerID == CustomerID)
                       .Sum (o => o.Amount);

基本上,它是一种设计接口以最小化冗长的方法,并提供一种自然且易读的方式来组合操作,以便用很少的代码完成很多工作。

对话框域的一个虚构示例:

DialogBoxAPI
.ModalDialogBox ()
.RoundCornersStyle ()
.BackgroundColor (RGB (200, 200, 200))
.TextColor (0, 0, 0)
.MessageText ("What shall we decide?")
.OKButton ()
.CancelButton ();

这会生成一个包含所提供特征的对话框。那是你在找什么?

答案 4 :(得分:1)

我对扩展方法和流畅呼叫的单一“上下文”结合匿名方法有很好的经验。

我希望这个例子会更清楚:

using System;
using System.Drawing;
using System.Windows.Forms;

namespace TcKs.FluentSample {
    class FluentSample {
        Form CreateDialogBox() {
            var frm = new Form();
            frm.AddTextField( "Simple text field:" )
                .AddTextField( "Advanced text field:", null, txt => txt.BackColor = Color.Red )
                .AddTextField( "Complex text field:", lbl => {
                    lbl.Click += ( _sender, _e ) => MessageBox.Show( lbl, "Some informative text.", "Help" );
                    lbl.Font = new Font( lbl.Font, FontStyle.Underline );
                    lbl.Cursor = Cursors.Hand;
                },
                    txt => {
                        txt.TextChanged += ( _sender, _e ) => txt.BackColor = txt.TextLength > 0 ? SystemColors.Window : Color.Red;
                        txt.DoubleClick += ( _sender, _e ) => { /* TODO: show lookup dialog */ };
                        txt.AddErrorProvider();
                    } )
                .AddButton( btn => btn.Click += ( _sender, _e ) => frm.Close() );

            return frm;
        }
    }

    // contains standard extension methods for fluent creation of control
    static class StandardControlFluentExtensionMethods {
        // this extension method create button and add them to parent
        public static T AddButton<T>( this T parent ) where T : Control {
            return AddButton<T>( parent, (Action<Button>)null );
        }
        // this extension method create button and add them to parent, then call initMethod
        public static T AddButton<T>( this T parent, Action<Button> initButton ) where T : Control {
            var button = new Button();
            parent.Controls.Add( button );
            if ( null != initButton ) { initButton( button ); }
            return parent;
        }
    }

    // contains specialized extension methods for fluent creation of control
    static class SpecializedControlFluentExtensionMethods {
        public static T AddCloseButton<T>( this T parent, Action<Button> initButton ) where T : Control {
            return parent.AddButton( btn => {
                var frm = btn.FindForm();
                if ( null != frm ) { frm.Close(); }

                if ( null != initButton ) { initButton( btn ); }
            } );
        }
    }

    // contains data-driven extension methods for fluent creation of control
    static class DataDrivenControlFluentExtensionMethods {
        public static TParent AddTextField<TParent>( this TParent parent, string title ) where TParent : Control {
            return AddTextField<TParent>( parent, title, (Action<Label>)null, (Action<TextBox>)null );
        }
        public static TParent AddTextField<TParent>( this TParent parent, string title, Action<Label> initTitle, Action<TextBox> initEditor ) where TParent : Control {
            Label lblTitle = new Label();
            // lblTitle .....
            if ( null != initTitle ) { initTitle( lblTitle ); }

            TextBox txtEditor = new TextBox();
            // txtEditor ....
            if ( null != initEditor ) { initEditor( txtEditor ); }

            return parent;
        }

        public static TParent AddErrorProvider<TParent>( this TParent parent ) where TParent : Control {
            return AddErrorProvider( parent, (Action<ErrorProvider>)null );
        }
        public static TParent AddErrorProvider<TParent>( this TParent parent, Action<ErrorProvider> initErrorProvider ) where TParent : Control {
            // create and/or initilaize error provider
            return parent;
        }
    }
}

答案 5 :(得分:0)

这是一个非常有趣的问题。我读了维基百科的文章,它有一个很好的例子。

http://en.wikipedia.org/wiki/Fluent_interface