Register views with regions depending on the user rights - wpf

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

Related

ReactiveUI, can execute doesn't disable button in WinForm

I got a simple WinForm application with a couple of textboxes and a confirm button, I'm using ReactiveUI.
This is my ViewModel:
public CurrencyViewModel()
{
editCurrency = new Currency();
this.ValidationRule(
viewModel => viewModel.IsoCode,
isoCode => !string.IsNullOrWhiteSpace(isoCode),
"error");
this.ValidationRule(
viewModel => viewModel.Name,
name => !string.IsNullOrWhiteSpace(name),
"error");
NewCommand = ReactiveCommand.Create(() => NewItem());
SaveCommand = ReactiveCommand.Create(() => Save(), this.IsValid());
}
public string IsoCode
{
get => isoCode;
set
{
editCurrency.IsoCode = value;
this.RaiseAndSetIfChanged(ref isoCode, value);
}
}
public string Name
{
get => name;
set
{
editCurrency.Name = value;
this.RaiseAndSetIfChanged(ref name, value);
}
}
private void NewItem()
{
IsoCode = string.Empty;
Name = string.Empty;
Symbol = string.Empty;
}
I then bind my validation and my save command in the view:
this.BindValidation(ViewModel, vm => vm.IsoCode, v => v.errorLabelIsoCode.Text).DisposeWith(disposables);
this.BindValidation(ViewModel, vm => vm.Name, v => v.errorLabelName.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCommand, v => v.sfButtonOk, nameof(sfButtonOk.Click)).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.NewCommand, v => v.sfButtonNew, nameof(sfButtonNew.Click)).DisposeWith(disposables);
My issue is that sfButtonOk stays enabled when i first launch the application even if isValid() is false, the command doesn't fire as intended so it's just a grapichal problem it seems. The button is disabled only if I write valid text and then cancel it.
It seems that the button is disabled only when isValid goes from true to false
The issue here is probably related to a view model being initialized too late, or due to the view model property not sending change notifications on the view side in time. Make sure you assign the view model to the IViewFor.ViewModel property before a call to WhenActivated, or otherwise implement INotifyPropertyChanged on the view side (also you probably don't need a WhenActivated at all because WinForms doesn't have dependency properties that might introduce a potential for a memory leak)
Additionally worth noting, that we have evergreen sample apps targeting various UI frameworks including Windows Forms in the ReactiveUI.Validation core repository https://github.com/reactiveui/ReactiveUI.Validation/blob/d5089c933e046c5ee4a13149491593045cda161a/samples/LoginApp/ViewModels/SignUpViewModel.cs#L43 Just tested the Winforms sample app, and the button availability seems to perform as we'd expect.

Add another ViewModelLocator convention in Prism (not override)

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

Can I remove the 'carrot' (upside down triangle) created by the ComboBoxListViewSkin?

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

callback functions for cakephp elements?

This might be a naive question since I am new to cakephp.
I am working on a project where there are many layouts, helpers and elements. Because this is a mutli-language site, I am intercepting the final rendered output to do some language conversion(so each visitor only sees his native language on the site including user input).
I've managed to convert most of the layouts and helpers by adding code in two places: AppController's afterFilter() and AppHeler's afterRender() function. But I can't figure out a centralized way to handle the elements and there are dozens of them.
So here are my questions: do all elements in cakephp have a common ancestor class? If so, does this ancestor class have callback functions like afterRender()?
Many thanks!
I'm not sure such a callback exists specific for 'elements', but looking at the source code, View::element() renders an element using the same _render() method as the View itself, and should trigger beforeRender() and afterRender()
Creating a custom View Class and Custom Callbacks
You may use a custom 'View' class and override the element() method, for example to have your own 'custom' callbacks being triggered in helpers
Something like this;
app/view/app_view.php
class AppViewView extends View {
/**
* custom 'element()' method, triggers custom
* before/aferRenderElement callbacks on all loaded helpers
*/
public function element($name, $params = array(), $loadHelpers = false)
{
$this->_triggerHelpers('beforeRenderElement');
$output = parent::element($name, $params, $loadHelpers);
$this->_triggerHelpers('afterRenderElement');
}
/**
* Names of custom callbacks
*/
protected $_customCallBacks = array(
'beforeRenderElement',
'afterRenderElement',
);
function _triggerHelpers($callback)
{
if (!in_array($callback, $this->_customCallbacks)) {
// it's a standard callback, let the parent class handle it
return parent::_triggerHelpers($callback);
}
if (empty($this->loaded)) {
return false;
}
$helpers = array_keys($this->loaded);
foreach ($helpers as $helperName) {
$helper =& $this->loaded[$helperName];
if (is_object($helper)) {
if (
is_subclass_of($helper, 'Helper')
&& method_exists($helper, $callback)
) {
$helper->{$callback}();
}
}
}
}
}
Then, in your AppController specify the 'view' class to use;
class AppController extends Controller {
public $view = 'AppView';
}

How can I create a fluent interface for defining Dialog Boxes?

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

Resources