如何创建一个流畅的界面来定义对话框?

发布于 2024-07-30 00:14:16 字数 645 浏览 6 评论 0原文

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

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

如果这会影响您的答案,UI 系统将基于 Winforms 或 WPF 构建。


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

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

文本框(“名称”)。 标记为(“人 姓名”)。 栏目(1)

文本框(“注释”)。 贴有标签(“注释”)。 多行(4)。 列(1).ToColumn(3)

但是,界面不必是单行


这个“如何使数据绑定类型安全并支持重构" 为数据绑定的流畅界面提供了一个良好的起点。

I am looking for examples and experience of using fluent interface to define simple Dialog Boxes (and other UI elements).

(I may need to add support for custom Dialog Boxes to an in-house programming language and I think a fluent interface may be the best way of doing it)

The UI system will be build on Winforms OR WPF if that effects your answers.


What if the interface is not fluent and I changed the question to just a “a simple to use (and read) API..” that does not depend on the use of a “drag and drop” UI designer.

I think the result will be fluent to some extend, e.g

Textbox(“name”). Labelled(“Person
Name”). Column(1)

Textbox(“notes”). Labelled(“Notes”).
Multiline(4). Column(1).ToColumn(3)

However the interface does not have to be a single line


This "How to make Databinding type safe and support refactoring"
gives a good starting point for a fluent interface for databinding.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

冷月断魂刀 2024-08-06 00:14:18

我在扩展方法和与匿名方法相结合的流畅调用的单一“上下文”方面拥有丰富的经验。

我希望例子会更清楚:

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;
        }
    }
}

I have good experience with extension methods and single "context" of fluent calling in combination with anonymous methods.

I hope example will be more clear:

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;
        }
    }
}
呆萌少年 2024-08-06 00:14:17

流畅接口的 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 ();

它将生成具有所提供特征的对话框。 这就是您要找的吗?

LINQ example of a fluent interface:

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

Basically, it is a way to design interfaces to minimize verbosity and provide a natural and well readable way to combine operations in order to accomplish much with little code.

An imaginary example for the dialog boxes domain:

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

Which would generate a dialog box with the supplied characteristics. Is that what you are looking for?

寄居人 2024-08-06 00:14:17

到目前为止给出的例子并不能降低任务的复杂性。 他们只将一种语法换成另一种(几乎同样冗长)语法。 如果您投入时间创建流畅的界面,请利用它来实际提高 API 的表达能力,而不仅仅是调整语法糖。 将抽象级别从默认基元(按钮、模式等)提升到模板、视觉继承链和行为。

我还没有完全想清楚这一点,但大致如下:

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

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

The examples given so far do nothing to reduce the complexity of the task; they only trade one syntax for another (almost equally verbose) one. If you invest the time to create a fluent interface, leverage it to actually improve the expressiveness of your API instead of just jiggling syntactic sugar. Raise the level of abstraction from the default primitives (buttons, modalities,...) to templates, visual inheritance chains and behaviors.

I haven't totally thought this through yet, but something along the lines of:

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

or

Dialog
 .UseErrorFormatting
 .SetTitleTo("Uh Oh")
 .Show()
忘年祭陌 2024-08-06 00:14:17

这个问题几天来一直让我抓狂。 我认为您可能需要问的一个问题是“为什么我应该为对话框创建一个流畅的 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 能给你带来任何好处。 我看到的只是在对话框上设置属性的方法,不提供任何可读性或值。

This question has been driving me crazy for a few days. I think a question you might need to ask is "why should I make a fluent API for dialog boxes?"

When you look at popular fluent APIs you'll notice something that's common with them in that it aids a user to be able to fluently read a line of code. Almost like a sentence. Observe:

From Ninject:

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

From Moq:

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

From the mother of all fluent APIs, Linq:

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

These are good APIs that provide almost a sentence structure to their use.

As another example, how is this:

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

More readable and more useful than

new Dialog()
{
     Buttons = Buttons.OkCancel,
     Title = "",
     Text = ""
};

And this is just a simple example. I noticed you are asking how to stuff things like layout, etc all in one line of code. My goodness your lines are going to be long.

I think you need to decide if you really think a fluent API is gaining you anything here. All I see are methods that set properties on a dialog box and don't provide any readability or value.

坠似风落 2024-08-06 00:14:16

我为我的对话框构建了一个流畅的界面,大致如下:

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 ) {
  //...
}

它从枚举生成按钮,并返回选定的按钮枚举值。

编辑:从评论中添加:

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

方法总结:

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

I built a fluent interface for my dialog boxes, something along the lines of:

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

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

I also had one for taking in an enum something like this:

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

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

Which generated the buttons from the enum, and returned the selected buttons enum value.

Edit: Added from comments:

The form it shows has its width calculated to fit all the buttons in one row.
It has an method for adding extra controls.
The layout is made from flow layout panels (one horizontal for buttons. one vertical for text and other controls)
The general layout is of a standard messagebox.
It has another option for Auto Accelerating the buttons.

Summary of Methods:

.Buttons(paramarray of DialogResult)
.FromEnum<T>(enum)
.Title(text)
.Text(text)
.Control(control)
.AutoAccelerate
.Icon(image)
.Show() as T
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文