views:

325

answers:

6

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.

+1  A: 

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?

Developer Art
I suggest .CornerStyle(CornerStyles.Round)
Dykam
@Dykam: It is an option, yes. It is up to the author to decide which style he prefers - with several options as a method parameter or several separate methods with no parameters.
Developer Art
True. Problem with your approach can be if there are multiple options, that the api gets cluttered.
Dykam
I know what a fluent interface is. Your dialog boxes example does not cover the hard bit of how to define the input fields, lables and layout
Ian Ringrose
+3  A: 

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
Pondidum
How well did it work, what problems did you hit?
Ian Ringrose
I hardly use the first example, but the second one i use all the time. i almost never have to create a special dialog box any more, just define an enum (or use an existing one) and use the dialog. Its only downfall is if you pass it a giant enum, with more than about 4 items, but you could add range filtering as an option of combat that.
Pondidum
This sample goes to show that there are some things a fluent interface is not good for, in my opinion. At least change those to prepositions like ".WithTitle(" and ".ContainingText(" if you feel like you must string along a bunch of property settings with dots in between.
Anderson Imes
how did you cope with defining input fields, labels and the layout?
Ian Ringrose
The Layout is fairly fixed, in that it looks like a standard messagebox, with the exception that you can put in your own buttons. so the width of the dialog is calculated. there is a function in it to allow other controls to be inserted ( .HasControl(new textbox) ) There is also a function to auto add accelerator keys
Pondidum
A: 

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

Jon
+2  A: 

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.

Anderson Imes
+1 for no gain from fluent API in this scenario. I agree.
Bernhof
+2  A: 

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()
Addys
+1  A: 

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