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.
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
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()
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;
}
}
}
Related
The project (WPF) has these folders:
Views
ViewModels
SubViews
SubViewModels
How to get the Prism's ViewModelLocator working with them (Views> ViewModels & SubViews> SubViewModels), the solution I found only works with one convention:
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
{
var viewName = viewType.FullName.Replace(".ViewModels.", ".CustomNamespace.");
var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
var viewModelName = $"{viewName}ViewModel, {viewAssemblyName}";
return Type.GetType(viewModelName);
});
}
You could opt for registration of the pairs (not as bad as it sounds because you have to register for navigation anyway).
Alternatively, you implement both conventions one after the other - take the view's name, subtract "Views" and add "ViewModels" and try to get the view model's type. If that fails, subtract "SubViews" and add "SubViewModels" and try again. You could even check for cross combinations (e.g. "SubViews.ViewA" and "ViewModels.ViewAViewModel")...
I solved it by checking the viewType and based on it, I return the appropriate ViewModel type:
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
{
string prefix;
if (viewType.FullName.Contains(".SubViews."))
{
prefix = viewType.FullName.Replace(".SubViews.", ".SubViewModels.");
}
else
{
prefix = viewType.FullName.Replace(".Views.", ".ViewModels.");
}
var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
var viewModelName = $"{prefix}ViewModel, {viewAssemblyName}";
return Type.GetType(viewModelName);
});
}
When implementing the java ComboBoxListViewSkin class to manage the popup listener of my ComboBox, this adds a 'carrot' to the upper left corner of the ComboBox (see below). If I remove this class implementation it goes away. I'm using the CombBoxListViewSkin's class popup listener to prevent the [SPACE] from selecting and closing the ComboBox when pressed which allows the [SPACE] character to be typed as part of an AutoComplete class.
This is all the code involved in managing and allowing the [SPACE] to work as part of AutoComplete class -and works great. I've tried searching the ComboBoxListViewSkin class for methods or properties that may prevent this, but nothing addresses this. I thought maybe the COMBO_BOX_STYLE_CLASS might offer something but everything really only manages the displaying, adding or removing items. Since the code below is the minimal necessary to recreate the issue, this will not perform the auto-complete function, but it demonstrates that removing and re-implementing the ComboBoxListViewSkin class causes the issue.... or appears to.
// Main method calling
public class Main extends Application{
public static void main(String[] args) {
launch();
}
public void start(Stage stage) throws Exception {
ComboBox cmb = new ComboBox();
cmb.getItems().setAll("One", "One Two", "One Two Three");
new ComboBoxAutoComplete(cmb);
Scene scene = new Scene(new StackPane(cmb));
stage.setScene(scene);
stage.setTitle("Test GUI");
stage.setWidth(300);
stage.setHeight(300);
stage.show();
}
}
// ComboBoxAutoComplete class with ComboBoxListViewSkin initialization
// Minimal of ComboBoxAutoComplete class constructor
import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.ComboBox;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import java.util.stream.Stream;
#SuppressWarnings("ALL")
public class ComboBoxAutoComplete<T> {
private ComboBox<T> cmb;
private String filter = "";
private ObservableList<T> originalItems;
private ComboBoxListViewSkin cbSkin;
public ComboBoxAutoComplete(final ComboBox<T> cmb) {
this.cmb = cmb;
originalItems = FXCollections.observableArrayList(cmb.getItems());
cbSkin = new ComboBoxListViewSkin(cmb);
// Aside from the variable declaration and initialization... this
// is the only ComboBoxListViewSkin code to handle the [SPACE]
cbSkin.getPopupContent().addEventFilter(KeyEvent.KEY_PRESSED, (event) -> {
if (event.getCode() == KeyCode.SPACE) {
filter += " ";
event.consume();
}
});
}
}
My expectation is for the ComboBox to look like all the other ComboBoxes in the application GUI. Although it is a minor issue, to the user I believe it may look like an issue with the application is going on.
Resolved: As Fabian suggested above, I added a cmb.setSkin(cbSkin) after the initialization and before the event filtering and it worked. Thought I would post so others would see it was resolved.
cbSkin = new ComboBoxListViewSkin(cmb);
cmb.setSkin(cbSkin); // <------------- ADDED
cbSkin.getPopupContent().addEventFilter(KeyEvent.KEY_PRESSED, (event) -> {
if (event.getCode() == KeyCode.SPACE) {
filter += " ";
event.consume();
}
});
I'm discovering .Net Core Tag Helpers and I was just curious to know if there are any tag helpers that replicate the #Html.DisplayFor. I think that the label tag helper replicates #Html.DisplayNameFor since it shows the property name on a model passed to the page, but is there an equivalent for #Html.DisplayFor for displaying a property value?
I'm assuming there isn't because in the microsoft .net core tutorials, razor pages that need to display the property value rather than the property name use the HTML helpers.
First, the tag helper is actually the "label asp-for". You can create a new tag helper that is a "label asp-text" helper.
Another option is to use another tag such as span and create a custom "span asp-for" tag helper.
Here is a simple span implementation:
[HtmlTargetElement("span", Attributes = FOR_ATTRIBUTE_NAME, TagStructure = TagStructure.NormalOrSelfClosing)]
public class CustomSpanTagHelper : InputTagHelper
{
private const string FOR_ATTRIBUTE_NAME = "asp-for";
public CustomSpanTagHelper(IHtmlGenerator generator) : base(generator)
{
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (output == null)
{
throw new ArgumentNullException(nameof(output));
}
var metadata = base.For.Metadata;
if (metadata == null)
{
throw new InvalidOperationException(string.Format("No provided metadata " + FOR_ATTRIBUTE_NAME));
}
if (!string.IsNullOrWhiteSpace(metadata.Description))
{
output.Content.Append(metadata.Description);
}
if (metadata.IsEnum)
{
var description = (this.For.Model as Enum).GetDescription();
output.Content.Append(description);
}
base.Process(context, output);
}
}
You will need to register your custom tag helper in your _ViewImports.cshtml like this: (don't forget to rebuild)
#namespace MyProject.Web.Pages
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#addTagHelper MyProject.Web.TagHelpers.CustomSpanTagHelper, MyProject.Web <-- custom import
I have a Ribbon Form with a treelist on the left so i put a XtraUserControl to insert a DocumentManager in which i would like to add all my tabbed forms (like in Visual Studio).
How can i do this?
Thanks
I suggedt you start from the How to: Display Documents Using a Tabbed UI example. The main idea of this example is that you can add the DocumentManager onto the form and then handle a treelist item's Click to add all needed child forms as MDI-children - the DocumentManager will track all the changes automatically:
Form childForm = new Form();
childForm.MdiParent = this;
childForm.Show();
To read more about the another Document Manager concepts and features please refer to the corresponding documentation articles.
public void Viewchild(Form _form)
{
//Check Before Open
if (!IsFormActive(_form))
{
_form.MdiParent = this;
_form.Show();
}
}
//Check If a Form Is Opened Already
private bool IsFormActive(Form form)
{
bool IsOpened = false;
//If There Is More Than One Form Opened
if (MdiChildren.Count() > 0)
{
foreach (var item in MdiChildren)
{
if (form.Name == item.Name)
{
// Active This Form
xtraTabbedMdiManager1.Pages[item].MdiChild.Activate();
IsOpened = true;
}
}
}
return IsOpened;
}
open form in
Master.frmBranch fb = new Master.frmBranch();
fb.Name = "frmBranch";
I have a TabControl as an ItemControl hosting a region, let's call it ContentRegion. Several modules register at least one view into the ContentRegion. But these registrations are made during module initialization.
I want to prohibit the registration of several views depending on the current user. But the user logs on after the module initialization and also can change during runtime.
Is there a way to provide a callback where prism can evaluate if the registration is active? Or do I have the chance to disable registrations of the region manager? Any other ideas?
The answer is quite simple: Implement a custom region behaviour. You just have to derive from the existing AutoPopulateRegionBehaviour:
public class SecurityEnabledAutoPopulateRegionBehaviour : AutoPopulateRegionBehavior
{
IUnityContainer container;
public SecurityEnabledAutoPopulateRegionBehaviour(IUnityContainer container, IRegionViewRegistry regionViewRegistry)
:base(regionViewRegistry)
{
this.container = container;
}
protected override void AddViewIntoRegion(object viewToAdd)
{
IRequiredAccessRight viewPermission = viewToAdd as IRequiredAccessRight;
if ( viewPermission != null )
{
ISessionManager sessionManager = container.Resolve<ISessionManager>( );
if ( sessionManager.AccessRights.IsGranted( viewPermission.RequiredAccessRight ) )
{
this.Region.Add( viewToAdd );
}
}
else
{
this.Region.Add( viewToAdd ); //The region does not require any permissions so we can proceed
}
}
}
The last step is to override all AutoPopulateRegionBehaviours or only on specific regions. How to achieve this is described in Appendix E of the Prism documentation in detail. What i did was to attach the behaviour only to a specific region and replace the AutoPopulateRegionBehaviour:
public partial class MyView : UserControl
{
public MainView( IUnityContainer container )
{
InitializeComponent( );
ObservableObject<IRegion> observableRegion = RegionManager.GetObservableRegion( ControlHostingTheRegion );
observableRegion.PropertyChanged += ( sender, args ) =>
{
IRegion region = ( (ObservableObject<IRegion>)sender ).Value;
region.Behaviors.Add( AutoPopulateRegionBehavior.BehaviorKey,
(SecurityEnabledAutoPopulateRegionBehaviour)container.Resolve( typeof( SecurityEnabledAutoPopulateRegionBehaviour ) ) );
};
}
}
You could bind the TabItem.Visibility to a variable that indicates if it should be shown or not. Once you checked the user rights, set this variable so that it hides unwanted tabs.
Another possibility would be to add the views to the regions after you checked the user rights, instead of registering the views with the regions.
IRegion detailsRegion = regionManager.Regions["DetailsRegion"];
detailsRegion.Add(view, viewName);
detailsRegion.Activate(view); // not sure if you need the Activate