How to create a double list view
There are three options for you to start.
Create the view from scratch
The only thing that your view needs to do to be a legal Site Manager view is to inherit from the base Site Manager view:
public partial class DoubleListView : LSOne.ViewCore.ViewBase
You will then have to overwrite key methods from the base class to get your functionality ready. To get an idea of which functions to overload, see the functions overview below.
Copy DoubleListView from the Hello World plugin
You can copy almost any view from the Site Manager and customize it based on your needs. To make this process simpler we have created view templates with minimal functionality included. You can find a template view for a double list view in the Hello World plugin from the Site Manager part of the development package. The view is called DoubleListView and you can copy it into your plugin. The view contains a shell for the basic functionality of a double list view.
Functions overview
Base functions
This is a list of functions that are most commonly overwritten to add logic to your view:
Base functions |
Description |
void LoadData( |
Here we load data into our top list view. This involves fetching the list of data objects and populating the list view with it. The isRevert variable is false unless the user pressed the Revert button above the Context bar. That function is supposed to revert to the last saved state of the view (basically reverting all changes that have not yet been saved). |
RecordIdentifier ID |
Returns the ID of the view and is used to determine if the view is already visible and does not need to be reloaded. Usually you return the ID of the object you are working with, but since you have a double list view you return RecordIdentifier.Empty |
string LogicalContextName |
Returns the string that should appear directly above the Context bar. |
void GetAuditDescriptors( List<AuditDescriptor> contexts) |
Connects the view with an audit view. See chapter Auditing for more details. |
void OnDataChanged(..) |
This is the opposite function of NotifyDataChanged(..) function that we covered in chapter How to create a simple view . This is the listener function and when another view or dialog wants to notify all open views about changes then this function is called. This function is mainly used to reload the list of objects to make sure we are showing the latest version of the data. |
List view operations
This is a list of common functions that are connected to the list views. Please note that these functions might not have the exact same names of the functions in your view because they usually contain the name of the list view (here we use the name topListView and btmListView for the two list views).
List view functions |
Description |
void topListView_SelectionChanged AND void btmListView_SelectionChanged |
These functions are called when the user changes the selected index of either list view. Here you need to check if the user clicked on a data object row or if they clicked outside the data rows. If they did not click on a data object row you need to disable the edit and delete buttons below the list view. Additionally, in the top list view function you need to hide the bottom list view as well. |
void topListView_RowDoubleClick AND void btmListView_RowDoubleClick |
These functions are called when the user double clicks on either list view. For the top list view you need to make sure that the user clicked on a data object row. If they did you should open the edit view or dialog. The same is true for the bottom list view, but note that when we do not allow the rows in the bottom list view to be edited then we do not have this function. |
void topListView_HeaderClick AND void btmListView_HeaderClick |
These functions are called when the user clicks on a column of either list view. This is usually used to order the results of the list views depending on the clicked column. Note that to keep the examples simple these functions are not used in the Hello World DoubleListView. |
void topListView_Opening AND void btmListView_Opening |
These functions are called when the user right-clicks on either list view. These functions are usually linked to the list views in the constructor. In this function you can add different operations that appear to the user in a drop-down list. |
Buttons operations
This is a list of the six functions behind the context buttons that are usually located below the two list views. Note that it is customary in the Site Manager to disable buttons that you cannot press. This means that the user should not be able to open an add dialog if they do not have permission to add the object.
Context buttons functions |
Description |
void btnsTop_AddButtonClicked AND void btnsBtm_AddButtonClicked |
These operations are called when the user presses the green plus buttons below the list views. For the top list view this usually triggers a dialog showing either all the fields that can be edited or just the mandatory fields. If only mandatory fields are shown then the user is redirected to a view for the data object where they can edit the optional parameters of the object. For the bottom list view it usually only shows a dialog and never a view. |
void btnsTop_EditButtonClicked AND void btnsBtm_EditButtonClicked |
These operations are called when the user presses the yellow pencil buttons below the list views. For the top list view this should either trigger a dialog where the data object can be edited (if it has a limited amount of information) or another view where the data object can be edited. For the bottom list view we usually use a dialog to edit and it is usually the same dialog as when adding. Note that when we don’t allow the editing of rows from the bottom list view then the edit button below the list view is not shown (see the Context property of the buttons) and then this function is not used. |
void btnsTop_RemoveButtonClicked AND void btnsBtm_RemoveButtonClicked |
These operations are called when the user presses the red minus buttons below the list views. This usually triggers the question, does the user really wants to delete the selected object (this is not a requirement)? If the user presses Yes then the data object is deleted through the data layer. |
Sample code walkthrough
public DoubleListView(RecordIdentifier selectedId)
: this()
{
this.selectedId = selectedId;
}
public DoubleListView()
{
InitializeComponent();
Attributes = ViewAttributes.Audit |
ViewAttributes.Close |
ViewAttributes.ContextBar |
ViewAttributes.Help;
btnsTop.AddButtonEnabled = PluginEntry.DataModel.HasPermission(Permission.DoubleListViewPermission);
lvTopObjects.ContextMenuStrip = new ContextMenuStrip();
lvTopObjects.ContextMenuStrip.Opening += LvTopObjects_Opening;
lvBottomObjects.ContextMenuStrip = new ContextMenuStrip();
lvBottomObjects.ContextMenuStrip.Opening += LvBottomObjects_Opening;
}
The list view has two constructors. An empty one where no row in the top list view should have focus or one with an ID parameter that makes the row from the top list view with the selected ID have focus when the view opens.
Everything else here is the same as in chapter How to create a single list view so please take a look there to get details on the Attributes and AddButtonEnabled. The only thing different here is the fact that we are connecting functions to both the ContextMenuStrips of the list views (these are used when the user right-clicks on the list views).
protected override void LoadData(bool isRevert)
{
LoadTopObjects();
}
private void LoadTopObjects()
{
RecordIdentifier oldSelectedID = selectedID;
lvTopObjects.ClearRows();
List<TopDataObject> dataObjects = Providers.TopObjectData.GetTopList(PluginEntry.DataModel);
foreach (TopDataObject obj in dataObjects)
{
Row row = new Row();
row.AddText(obj.Text);
row.AddText(obj.ExtraInfo);
row.Tag = obj.ID;
lvTopObjects.AddRow(row);
if (oldSelectedID == obj.ID)
{
lvTopObjects.Selection.Set(lvTopObjects.RowCount - 1);
}
}
lvTopObjects.AutoSizeColumns();
lvTopObjects_SelectionChanged(this, EventArgs.Empty);
}
Here we are loading data into the top list view.
This function is the same as chapter How to create a single list view , so please take a look there to see details about the LoadObjects() function.
The only thing to note here is that the function lvTopObjects_SelectionChanged is responsible for showing the bottom list view. When the selection is changed in the top list, we need to populated the bottom list with the data corresponding to the selected object, or hide the bottom list if there is nothing selected in the top list.
private void LoadBottomObjects()
{
if(lvTopObjects.Selection.Count == 0) return;
lvBottomObjects.ClearRows();
RecordIdentifier selectedTopObjectID = (RecordIdentifier)lvTopObjects.Selection[0].Tag;
List<BottomDataObject> dataObjects = Providers.BottomObjectData.GetBottomList(PluginEntry.DataModel, selectedTopObjectID);
foreach (BottomDataObject obj in dataObjects)
{
Row row = new Row();
row.AddText(obj.Text);
row.AddText(obj.SomeProperty);
row.Tag = obj.ID;
lvBottomObjects.AddRow(row);
}
lvBottomObjects.AutoSizeColumns();
lvBottomObjects_SelectionChanged(this, EventArgs.Empty);
}
Here we are loading data into the bottom list view.
This function is almost identical to the LoadTopObjects() function previously covered with the exception that the data layer function requires the selection from the top list view so we get that selection in the variable selectedTopObjectID.
public override void OnDataChanged(DataEntityChangeType changeAction,
string objectName,
RecordIdentifier changeIdentifier,
object param)
{
if (objectName == "dataObject")
{
LoadItems();
}
}
Here we have the OnDataChanged(..) function that is used to respond to data changes in other views.
This function is the same as in chapter How to create a single list view where you can see details about the OnDataChanged(..) function.
private void lvTopObjects_SelectionChanged(object sender, EventArgs e)
{
bool objectSelected = (lvTopObjects.Selection.Count != 0);
RecordIdentifier selectedObjectId = objectSelected ? (RecordIdentifier)lvTopObjects.Selection[0].Tag : "";
btnsTop.EditButtonEnabled = btnsTop.RemoveButtonEnabled = objectSelected;
if (objectSelected)
{
if (!lblGroupHeader.Visible)
{
lblGroupHeader.Visible = true;
lvBottomObjects.Visible = true;
btnsBottom.Visible = true;
lblNoSelection.Visible = false;
}
LoadBottomObjects();
}
else if (lblGroupHeader.Visible)
{
lblGroupHeader.Visible = false;
lvBottomObjects.Visible = false;
btnsBottom.Visible = false;
lblNoSelection.Visible = true;
}
}
private void lvBottomObjects_SelectionChanged(object sender, EventArgs e)
{
bool objectSelected = (lvBottomObjects.Selection.Count != 0);
btnsBottom.EditButtonEnabled = btnsBottom.RemoveButtonEnabled = objectSelected;
}
Here we have two selection-changed functions, one for each list view.
The function for the bottom list view (lvBottomObjects_SelectionChanged) is simple and just like the one for a single view. We are simply enabling or disabling the bottom Edit and Remove buttons based on if an item was selected in the list view.
The function for the top list view (LvTopObjects_SelectionChanged) is more complex. First we do the same thing as we do for the lower list view, enabling or disabling the edit and remove buttons. But then we need to manage the lower-list view. We show or hide the appropriate controls depending on if a data object was selected or not. If a data object was selected then we also load the lower list view with the function LoadBottomObjects()
private void lvTopObjects_RowDoubleClick(object sender, EventArgs e)
{
if (btnsTop.EditButtonEnabled)
{
btnsTop_EditButtonClicked(this, EventArgs.Empty);
}
}
private void lvBottomObjects_RowDoubleClick(object sender, EventArgs e)
{
if (btnsBottom.EditButtonEnabled)
{
btnsBottom_EditButtonClicked(this, EventArgs.Empty);
}
}
These two functions deal with double clicks on both the list views. In both functions we check if the edit button below the list view is enabled (if it is disabled then the user did not double click a data row) and if it is then we run the operation behind the edit button.
void LvTopObjects_Opening(object sender, CancelEventArgs e)
{
ContextMenuStrip menu = lvTopObjects.ContextMenuStrip;
menu.Items.Clear();
var item = new ExtendedMenuItem(
Properties.Resources.Edit,
100,
btnsTop_EditButtonClicked);
item.Image = btnsTop.EditButtonImage;
item.Enabled = btnsTop.EditButtonEnabled;
item.Default = true; // The default item has a bold font
menu.Items.Add(item);
item = new ExtendedMenuItem(
Properties.Resources.Add,
200,
new EventHandler(btnsTop_AddButtonClicked));
item.Enabled = btnsTop.AddButtonEnabled;
item.Image = btnsTop.AddButtonImage;
menu.Items.Add(item);
item = new ExtendedMenuItem(
Properties.Resources.Delete,
300,
new EventHandler(btnsTop_RemoveButtonClicked));
item.Image = btnsTop.RemoveButtonImage;
item.Enabled = btnsTop.RemoveButtonEnabled;
menu.Items.Add(item);
e.Cancel = (menu.Items.Count == 0);
}
This function deals with right-clicking the list view. They are covered in more detail in chapter How to create a single list view , but the most important part is that these functions are rarely changed. For the bottom list view a similar function can be used with the appropriate buttons and event handlers. For each list, buttons can be added or removed from the drop down.
private void btnsTop_EditButtonClicked(object sender, EventArgs e)
{
RecordIdentifier selectedId = (RecordIdentifier)lvTopObjects.Selection[0].Tag;
PluginOperations.ShowTopObjectView(selectedId);
}
private void btnsTop_AddButtonClicked(object sender, EventArgs e)
{
var dlg = new Dialogs.TopObjectDialog();
if (dlg.ShowDialog() == DialogResult.OK)
{
LoadTopObjects();
}
}
private void btnsTop_RemoveButtonClicked(object sender, EventArgs e)
{
if (QuestionDialog.Show(
Properties.Resources.DeleteTopObjectQuestion,
Properties.Resources.DeleteTopObject) == DialogResult.Yes)
{
RecordIdentifier selectedId = (RecordIdentifier)lvTopObjects.Selection[0].Tag;
PluginProviders.TopObjectData.Delete(PluginEntry.DataModel, selectedId);
LoadTopObjects();
}
}
Here we have three functions for the three buttons below the top list view. These are the same buttons that we have in chapter How to create a single list view , where they are covered in more detail.
private void btnsBottom_EditButtonClicked(object sender, EventArgs e)
{
RecordIdentifier selectedTopId = (RecordIdentifier)lvTopObjects.Selection[0].Tag;
RecordIdentifier selectedBottomId = (RecordIdentifier)lvBottomObjects.Selection[0].Tag;
var dlg = new Dialogs.BottomObjectDialog(selectedTopId, selectedBottomId);
if (dlg.ShowDialog() == DialogResult.OK)
{
LoadBottomObjects();
}
}
private void btnsBottom_AddButtonClicked(object sender, EventArgs e)
{
RecordIdentifier selectedTopId = (RecordIdentifier)lvTopObjects.Selection[0].Tag;
var dlg = new Dialogs.BottomObjectDialog(selectedTopId);
if (dlg.ShowDialog() == DialogResult.OK)
{
LoadBottomObjects();
}
}
private void btnsBottom_RemoveButtonClicked(object sender, EventArgs e)
{
if (QuestionDialog.Show(
Properties.Resources.DeleteBottomObjectQuestion,
Properties.Resources.DeleteBottomObject) == DialogResult.Yes)
{
RecordIdentifier selectedId = (RecordIdentifier)lvBottomObjects.Selection[0].Tag;
PluginProviders.BottomObjectData.Delete(PluginEntry.DataModel, selectedId);
LoadBottomObjects();
}
}
Here we have three functions for the three buttons below the bottom list view. These are very similar to the buttons for the top list view. The difference here is that we need to access the bottom list view to get the ID of the selected item. Another difference is that when editing an item we use the same dialog that we use when adding an item. We only use a different constructor for Add and Edit.
Next up is the first exercise in Lesson 5: Lesson 5 - Plugins: views, tabs and dialogs