MVP - Showing/hiding controls based on business logic - controls

What would be the best way of going about this? I have a method in the presenter that populates various textboxes using a switch statement, but also needs to make sure that only these textboxes are visible, eg.:
switch (operation.CalculationType) {
case CalcType.Type1:
textbox1.Visible = true
_view.TextBox1 = "some value";
break;
case CalcType.Type2:
textbox1.Visible = true;
textbox2.Visible = true;
_view.TextBox1 = "some value";
_view.TextBox2 = "another value";
break;
I'm not fond of the idea of exposing a Visible property for each control on the form (theoretically this could lead to exposing all sorts of properties, which just seems wrong to me). Another idea I had was to create a method or event that the presenter calls, telling the form to show/hide the controls, but that kind of means replicating the logic in the presenter.
So what's the "proper" way of doing something this?
Thanks

If the variable _view is not an interface you should make it one, implement it and then add a method or methods that setup which textboxes are visible. This way it is clear in the code what you are trying to do and it not tied to that particular form implementation.
Inteface IFormView
Sub DisplayType1
Sub DisplayType2
....
End Interface

Related

passing control indexer by reference to method to change its property

i have made a program, which include a lot of controls. The controls would be showed and hided according to the choice of the user. That means that controls overlapped on each other at design time. now i want to change the forecolor and backcolor of all controls at design time. but i founded so hard to accomplish this task, because all the control overlapping each other. so I decided to make a for loop method to iterate the controls in the form and then check each control in turn whether it has controls. when the control has also controls in it, I call the same method and pass the control to it to change the properties for the subcontrols too. The method like so:
void setColor(ref Control con)
{
con.BackColor= System.Drawing.Color.Black;
con.ForeColor=System.Drawing.Color.Yellow;
if (con.Controls.Count > 0) { setColor(ref con); }
}
so my Form include tabControl with multiple tabPages. I iterate the tabPages and wanted to pass it to this method, but I become error "Indexer may not be passed as an out or ref parameter"
I pass it so: setColor(ref tabControl1.Controls[i]);
can you please help me to solve this problem?
I have resolved the problem.
I have removed the "ref" from method and wrote the method simply like the following:
void SetColor(Control con)
{
con.BackColor = System.Drawing.Color.Black;
con.ForeColor = System.Drawing.Color.Yellow;
if (con.Controls.Count > 0)
{
for (int i=0; i<con.Controls.Count;i++)
SetColor(con.Controls[i]);
}
}
and call it so: setColor(this.Controls[i]);

How to change base layer using JS and leaflet layers control

I have to modify existing application, where leaflet layers control is used - I need to display one of the base layers when the map is initiated. Is there a way, how to call some function from the layers control from JS script - something like control.select(1) ....? If not, how can add a tile layer in the same way as it is done by the control - when I add new L.TileLayer during map init, it's not overwritten by manual layers control selection change?
You could try to emulate a user click on the Leaflet Layers Control, but there is a much more simple way to achieve what you initially describe.
Normally by simply adding a layer to the map (e.g. myTileLayer.addTo(map)), if that layer is part of the base layers or overlays of the Layers Control, the latter will automatically update its status (if you added a base layer, the radio buttons will be selected accordingly; for an overlay, the corresponding checkbox will be ticked).
Now I am not sure I understood properly your last part ("when I add new L.TileLayer during map init, it's not overwritten by manual layers control selection change").
If you mean you have an unexpected behaviour because the Tile Layer you added is not changed by the Layers Control, it may be due to the fact that you are not re-using a Tile Layer that the Layers Control knows: do not use new L.TileLayer, but re-use one of the base layers or overlays.
For example:
var baselayers = {
"Tile Layer 1": L.tileLayer(/* ... */),
"Tile Layer 2": L.tileLayer(/* ... */),
"Tile Layer 3": L.tileLayer(/* ... */)
};
var overlays = {};
L.control.layers(baselayers, overlays).addTo(map);
baseLayers["Tile Layer 1"].addTo(map);
There are several ways to handle this problem.
1) You can select second baselayer by clicking on the radio input in layer control. This can be done programatically like this (not recommended):
var layerControlElement = document.getElementsByClassName('leaflet-control-layers')[0];
layerControlElement.getElementsByTagName('input')[1].click();
2) Just change the order of baseLayers passed into L.Control.Layers during initialization.
3) Extend L.Control.Layers so that it accepts some new option {"selectedBaseLayerIndex": 1}
I found this after digging in the leaflet code:
1) find the layer you want to display in control's structure _layers
2) call map.addLayer(_layers[your_layer_index].layer)
3) find your layer in control's structure _form
4) set it's checked attribute to true
Thank you ghybs. You help me to understand leaflet.
I keep base-map preference in FireBase and get it back on connection to store via Redux.
Now my Map component re-render with tileLayer from Redux.
Before I tried to pass it on props... But with leaflet, like ghybs says, you have to add it again to the map, even if you gave it with something like :
const mapRef = useRef(); //Useful to reach Map leaflet element
layerRef.current = L.control
.layers(baseMaps, null, { position: "topleft", sortLayers: true})
.addTo(map);
And after, I hook my tileLayer :
useEffect(() => {
const { leafletElement: map } = mapRef.current; //Don't forget the current...
baseMaps[tileLayer].addTo(map);
}, [tileLayer]);
return (
<Map
onbaselayerchange={(ev) => handleBaseLayerChange(ev.name)}
layers={defaultLayer(tileLayer)}
ref={mapRef}
{...fieldProps}>
<CustomersMarkers layer={layerRef} customers={customers} />
</Map>
);
If you are using jQuery you can simulate a click on the Layers control to change the base layer:
$('.leaflet-control-layers-selector')[0].click()
If you want to switch to the second map layer use the next index [1]

Implementing multiple dialogs with similar processing

We have multiple dialogs in our MFC program that are very similar. Each one of these dialogs contain similar controls (i.e., they all contain a name, date, address, etc). Because of this, we've had to code out the display code multiple times for the windows despite the fact that the processing of these controls is identical. I'm looking for suggestions on how to change up our guis so that i have to only do the processing at one spot and not have to do it multiple times.
My thought was to have a class that would do the processing and pass pointers to the controls to display to that class, though i feel that is not a very good OO design.
Thoughts?
Create a base class derived from CDialog (say CMyDlgBase), place all your common functions there and derive your dialog classes from CMyDlgBase instead of CDialog.
You can now call the functions in CMyDlgBase as if they were declared directly in your dialog classes.
EDIT sample code to validate an item common to dialogs (CDlg1 and CDlg2 are derived from CMyDlgBase), error checking code not included:
BOOL CMyDlgBase::ValidateName(UINT nID)
{ CString ss;
CEdit *pEdit = GetDlgItem(nID);
pEdit->GetWindowText(ss);
if (ss.Find(_T("A")) < 0) // some kind of validation
{ MessageBox(_T("Name should contain the character 'A'"));
pEdit->SetFocus();
return FALSE;
}
return TRUE;
}
CDlg1::OnOK()
{ if (!ValidateName(IDC_DLG1_NAME)) // resource id value = 101
return;
CDialog::OnOK(); // This will close the dialog and DoModal will return.
}
CDlg2::OnOK()
{ if (!ValidateName(IDC_DLG2_NAME)) // resource id value = 102
return;
CDialog::OnOK(); // This will close the dialog and DoModal will return.
}

MvcContrib Pager - Change Page Size

I'm not using a Grid, just using the MvcContrib Pager. I have a partial view created for the Pager (so I can display it at top and bottom of the results easily), and it calls the #Html. Pager method as so:
#Html.Pager(Model.PagedPrograms).First("<<").Last(">>").Next(">").Previous("<").Format("Item {0} - {1} of {2} ")
This works without additional tweaking as long as all parameters are passed to the page via QueryString, since Pager knows to rebuild those back on the URLs.
I'd like to give the user the option to change the page size (say 20, 50, All) ... I can easily handle that on the controller end, and I could write something like
#if (Model is Foo) {
#Html.ActionLink<SearchController>(sc => sc.Foo(var1, var2, var3, 20), "20")
#Html.ActionLink<SearchController>(sc => sc.Foo(var1, var2, var3, 50), "50");
#Html.ActionLink<SearchController>(sc => sc.Foo(var1, var2, var3, -1), "All");
}
But I would have to do that for each Model type that might use this Pager... I might be overthinking this or coming at this completely backwards, but I thought I'd ask and see if anyone had insight.
Currently the Pager is only called from a view which takes IPagedProgramList (provides IPagination<ProgramDTO> { get; }), and I have two ViewModels implementing that interface (a simple search and an advanced search). But if this project grows and we add new ViewModels that use that Interface I would have to update the Pager partial view, and that seems bad / doesn't scale / etc.
So a nod to Ek0nomik who got me thinking outside the box on this one.
Step 1: Make sure all pages that are going to use the Pager controller are passing all parameters via GET not POST. Use RedirectToAction if you must accept post somewhere and just translate all the parameters into primitive types for the GET method.
Step 2: Don't worry about adding .Link() to the Pager. As long as everything's coming in via GET, you're fine. It will look at the URL for the page and adjust the page number parameter as it needs to when you're going forward/back.
Step 3 (optional but recommended): For consistency across your application, somewhere (probably your Global.ascx.cs file) you should define a list of the page sizes you will support. In my case I used Dictionary<int,string> so that I could pass -1 as the PageSize value but display All (and of course the data layer must know that -1 means disable paging).
Step 4: Add something like this to your pager partial view:
<ul class="pageSizeSelector">
#foreach (KeyValuePair<int,string> kvp in MvcApplication.AVAIL_PAGE_SIZES)
{
<li>#kvp.Value</li>
}
</ul>
Step 5: The javascript function changePageSize is so simple, I couldn't believe I hadn't thought of this first (note: no jQuery required... just in case you're not already using it, you don't need to just for this).
function changePageSize(size) {
var PSpattern = /PageSize=\d+/i;
var url = window.location.href;
url = url.replace(PSpattern, "PageSize=" + size);
window.location.href = url;
}
Step 6 (optional, unless you're an Internet Troll): Profit!

Big switch in the view

I'm new to MVC and php framework, so please excuse me for this simple question...
I like to have my views without big chunk of php code but I have a case where I don't really know how to do it properly.
Basically some object has 20+ different states and the state is given by the model.
Now I have a :
switch($object->getState())
{
case 0:
$sText = '...';
break;
case 1:
$sText = '... on the'.$object->getDate();
break;
...
case 20:
$sText = '...';
break;
}
?>
<img src="<?echo $object->getState()?>.png" alt = "<?echo $sText;?>"
title = "<?echo $sText;?>" />
How can I do that without the 40+ lines of php in the view ? I don't want to have to repeat the <img> tag 20 times. For me the text should belongs to the view, not the model.
Maybe a view helper that will assign the text to the state ?
A view helper isn't going to make your code any less ambiguous or better MVC. The text for the view might not belong to the Model, but the logic for determining the text definitely doesn't belong to the view. There's nothing wrong with writing a method like $model->getViewTextForState($object->getState()) - that's basically the same approach as you'd use for string localization for numerous languages.
Think of it this way - the alt text for the view really does belong to the model because the model is responsible for marshalling ALL of the data. If some text in the view is variable, then it is literally model data, just like the image name you're producing from the $object->getState() method. The image name and the alt text for it are data and should be provided to the view from the model with a single line access method
The switch could be in your action, and you could use translation strings like this
$this->sText = 'object_state_' . $object->getState()
then in your view, translating $sText would do the trick.

Resources