收录日期:2020/06/02 02:17:32 时间:2009-08-26 11:26:44 标签:wpf,winforms,design,user-interface,fluent-interface

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.

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()

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

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.

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?

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

This was a very interesting question. I read up the wikipedia article on it, and it had a superb example.

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