Microsoft Access 2007 Ribbon

G

Guy

Hi All,

I am upgrading a large .ADP application from Office 2003 to Office 2007 and
would like some feedback on the best way to manage Ribbons in Access.

I am in the process of converting all the old custom menubar/toolbar
functionality into equivalent/similar/improved functionality using the Ribbon

Currently I have over 20 Ribbons (and growing) all use the "Start from
Scratch=True" attribute. Obviously I would prefer to utilise less Ribbons,
but by the same token the prospect of dynamically applying lots of
customisation to fewer Ribbons when each form or report is opened appears
equally onerous. Needless to say I am still learning about how to best manage
the Ribbon and hence the reason for this posting

The biggest problems I currently have are simple issues such as
enabling/disabling controls on the Ribbon dynamically according to actions a
user might take on any given form.

To force a ribbon control refresh requires you to "Invalidate" that control
on the ribbon, however, this requires a reference to the ribbon and I have
over 20 of them.

I have tried storing the references in a collection using the Ribbon
"OnLoad" callback:

Public mcolRibbon As New Collection

Public Function OnLoad(ByVal Ribbon As IRibbonUI)
mcolRibbon.Add Ribbon, Screen.ActiveForm.RibbonName
End Function

However when I try to retrieve the reference perhaps in the Form_Open event

Dim objRibbon as IRibbonUI
Set objRibbon = GetRibbon(me.RibbonName)

Public Function GetRibbon(strRibbonName as String) As IRibbonUI
GetRibbon = mcolRibbon.Item(strRibbonName)
End Function

or invalidate the control from the from by calling:

Public Sub gsubInvalidateControl(strRibbonName As String, strControl As
String)
mcolRibbon.Item(strRibbonName).InvalidateControl (strControl)
End Sub

it fails.

There appear to be two issues:

1) If the Ribbon has not yet been loaded the open Form action causes the
Ribbon to Load, however, the Form_Open event occurs before the Ribbon_OnLoad
event meaning that a Null Reference is returned.

2) I don't use custom collections very often and I may have the syntax
incorrect, but I am also experiencing problems accessing the Ribbon object
from within the collection.

Can anyone assist in either resolving the issues I am having or suggest a
better approach.

Your Assistance Appreciated
Guy
 
A

Albert D. Kallal

Ah...

I like you idea of building a collection.

I really feel bad that I don't have more time to write up a nice article.

However, a few things:

The active screen is not set until AFTER both on-open , and then on-load ARE
COMPLETE.
(I know this, because I have for years use the on-open, or even the on-load
event of the form
to pick up the PREVIOUS form by going:

set frmPrevous = screen.ActiveForm.

So, that puts a bit of damper on your approach to pick up the active
screen...(you can't do that from on-load, or on-open events of a form...you
get the previous screen!!!

and, normally, a *great* trick is to place the public function in a form, as
menu items will call that first before the same function name in a public
module (but, again..the form is not yet consider loaded. -- DO NOTE that
once the ribbon is loaded, then routines declared public in your form that
HAS THE FOCUS will be called. This allows some really nice custom code for
each form, but the SAME ribbon to be used.

Anyway, you can't use screen.active.

2nd thing:
mcolRibbon.Item(strRibbonName).InvalidateControl (strControl)

You do realize that invalidate method only causes the xml mark-up text to be
re-executed for that control. So, for setting the controls visible property,
you actually have to maintain that value in memory (in the old menu bars,
the menu control it self held the state of the control. This is NOT the case
with a ribbon. (it possible you realize this...as you not posted any xml
ribbon code)..

So, what this means is that you can hard code the mark-up, but that is of no
use. The solution is thus to use a function, such as:

<button id="button1" label="Buttion1"
getVisible="MyVisible"
onAction="=MyTest1()"/>

While we at this, it important to note that the mark-up text IS CASE
sensitive (I read 20+ articles about the ribbon, and NOT ONE has mentioned
this -- I just trying to save you some lessons that I learned!!!.

Note that we have a function called MyVisble. In theory, you need a
variable declared for EACH control that you plan to have visible, or not
visible.

The ONLY difference between using

mcoIRibbion.Invalide

and

mcoRibbion.InvalideCotnorl(strContlr)

Is that the 2nd example only runs the code for the ONE control. THIS IS THE
ONLY DIFFERENCE. In other words, the same piece code would get called in
both cases, but the InvalideContrll is better code because it will not call
each and every piece of code for each button on the ribbon.

So, keep in mind, the invalide call does not actually make the control
visible, or invisible...it is the setting for the function in the he call
back that determines this. and *further* keep in mind that the code called
will be the same both cases, but it better to use the Invalidecontrfll as
you not wind up having every control on the bar get called. (however, you
code MUST STILL work for that case).

eg:

Public Sub MyVisible(control As IRibbonControl, _
ByRef visible As Variant)

static m_buttion1 as boolean
static m_buttion2 as boolean
select case contorl.id

case "buttion1"

visible = m_buttion1

case "buttion2"

End Sub

In the above, "id" will be text string (buttion1 from above) in BOTH cases

eg:

mcoribbion.Invalide
or
mcoribbion.InvalideContorl("buttion1")

It is the *system* that provong the value for contorl.ID in the above. For a
small robbionk you could always use the Invalide, and never have to use the
InvalideContrl method.

So, what the above means is that you need a Boolean variable for each
control that I plan to turn off, and turn on to hold the state of that
control.

(or a separate "getVisible" function for each control that you plan to make
visible/non visible).

Without question, building a custom collection is a great idea. with a bit
of work you could near the same syntax of what we been using for years....

I have to run out the door right now. I *really* need to write an article on
this......
 
G

Guy

Hi Albert,

Thank you for your comprehensive response.

I wasn't aware that once the form was loaded/opened the forms public
functions would get called in preference to module public functions. This
could be useful so I will investigate this aspect further.

Ideally I would like to use just 3 Ribbons:

1) Home - Which allows quick access to key forms, reports and tools such as
database backup within the application and is shown only when the Main Menu
is active.

2) Form - Which shows for each form the "Home" button which takes you back
to the Main Menu and Home Ribbon and the standard Records/Filters/Cut and
Paste groups plus custom Command actions that are relevant to that form. So
for instance if it was the "Customer Invoice" form you could print the
Customer Invoice or if it was the "Budget" form you could export the budget
to Excel. Some forms would have many Command actions available.

3) Report - Which shows for each report the "Home" button plus the standard
print/export/send mail groups and so on. It is unlikely that many custom
Command actions would be required for this Ribbon.

Currently I have about 20 individual Form Ribbons, however, in your opinion
would it be better to have just 1 Form Ribbon and customise it appropriately
when each form becomes active?

Assuming that there are pros/cons to both the above approaches and that we
are happy to continue with the multiple Form Ribbon method. The next question
is maintaining references to each Ribbon so that we can hide/show or
enable/disable etc the Form Command actions.

As previously stated I have tried to maintain the Ribbon references in a
custom Collection which is loaded via the Ribbon "OnLoad" event. However, I
am having problems with:

1) Event timing
2) Populating and Accessing the reference in the Collection

If I can't use the Screen.ActiveForm mechanism to determine which Ribbon is
calling the "OnLoad" callback then how do I uniquely "Key" the collection,
the "Tag" XML attribute doesn't appear to be supported at the CustomUI level?

Is there no method by which the active form can refer to the active ribbon
without trying to access a stored reference passed by the "Onload" callback?

If I can populate the Collection then I also find that I am having problems
accessing the Ribbon IRibbonUI object from within the collection. It maybe
that I haven't populated the collection correctly or it maybe that I am not
using the incorrect syntax.

Like yourself I have searched Microsoft and the Web and looked at a number
of sample databases using RibbonX, but none of them tackle the issues of
multiple (more than one) Ribbon references. Which leads me to conclude that
perhaps 1 Ribbon and lots of customisation maybe the better method, although
onerous.

Yes I can think of solutions without using a collection such as a unique
Onload callback for each Ribbon along with a separate variable to maintain
each reference, but this
feels like a "kludge" option.

Are you able to assist with any further thoughts or code samples.

Your assistance appreciated

Guy
 
G

Guy

Albert,

Just thought of another good reason why I can use Screen.ActiveForm, apart
from the fact that it doesn't work, it maybe a report that is causing the
Ribbon "OnLoad" event.

Guy
 
A

Albert D. Kallal

Guy said:
Hi Albert,

Thank you for your comprehensive response.

I wasn't aware that once the form was loaded/opened the forms public
functions would get called in preference to module public functions. This
could be useful so I will investigate this aspect further.

Actually, a97 to a2003 worked this way also. So, most of my custom menu bars
in previous versions DID in fact call code in the current active form.

This does man that some forms did share the same menu bar. However, in all
practical use, most of my complex forms did have a custom menu bar, but that
menu bar for the most part did call code in the current active form (it
makes sense to place code for a form in the form code module).
Ideally I would like to use just 3 Ribbons:

1) Home - Which allows quick access to key forms, reports and tools such
as
database backup within the application and is shown only when the Main
Menu
is active.

I think in the above case, I would simply build a xml ribbon,, and then
specify this ribbon in the forms "other" tab. This will load up the ribbon
for you, and you not need any code.
2) Form - Which shows for each form the "Home" button which takes you back
to the Main Menu and Home Ribbon and the standard Records/Filters/Cut and
Paste groups plus custom Command actions that are relevant to that form.
So
for instance if it was the "Customer Invoice" form you could print the
Customer Invoice or if it was the "Budget" form you could export the
budget
to Excel. Some forms would have many Command actions available.

Sure, to include the standard paste, cut etc, you can either build you own
"group" that you add to each ribbon. However, it is MUCH easier to include
the standard cut/paste menu "group" in your ribbon that you build for the
current form.

To make you "own" clipboard, you could use:

<group id="Clipboard" label="Clipboard">
<control idMso = "Copy"/>
<control idMso = "Cut"/>
<control idMso = "Paste"/>
</group>

however, it much easier to include the built in ones. For example, lets
include both built in clipboard, and the "find" and filter group. You can
go:

<group idMso="GroupClipboard" />
<group idMso="GroupFindAccess" />
<group idMso="GroupSortAndFilter" />

So, it is really easy to add the standard cut/copy to your existing ribbon.
3) Report - Which shows for each report the "Home" button plus the
standard
print/export/send mail groups and so on. It is unlikely that many custom
Command actions would be required for this Ribbon.

Right...and again, I would build a new ribbon, but simply include some of
the report groups. You do know how to "find" the above group names? (and as
I mentioned...remember...they are case sensitive).

To find the id of a command, or group, simply go:

office button->access options (lower right)
customize

Note in the customizing window if you hold your mouse over a command button,
you get the isoID of that button (it that last value enclosed in ()).

NOTE VERY clearly how in addition to the buttons, the "groups" for each of
those buttons are ALSO IN that list. So, you can easily grab the group for
your report....

Currently I have about 20 individual Form Ribbons, however, in your
opinion
would it be better to have just 1 Form Ribbon and customise it
appropriately
when each form becomes active?

I actually think it ok to have a ribbon for each form, especially if that
ribbon has form specify commands. so, sure, "include" the standard clip
group, and perhaps a general close button group for each form. but, I would
certainly continue to build a ribbon for each form.
Assuming that there are pros/cons to both the above approaches and that we
are happy to continue with the multiple Form Ribbon method. The next
question
is maintaining references to each Ribbon so that we can hide/show or
enable/disable etc the Form Command actions.

Ok, this is tough part! I just finished coding a solution that allows me to
use a collection to "enable" and "disable" a button by name. However, the
code is not finished, has some problems. However, it is *perfect* for
migration existing applications, and you can simple code in the form as you
always done.

eg:

MyRibbon.MyControls("button1").Enabled = True
or
MyRibbon.MyControls("button1").Visible = True
or
MyRibbon.MyControls("button1").Label = True

The MyContorls collection is actually automatically populated by the ribbon
load command I have. So, right now, this code is running as I write this,
but there are still some design issues that need to be addressed.

However, since my code is not ready, then your going to have to write code
for each button that you want to enable, and disable. And further, you going
to have to place that code in a standard code module (not the forms code
module). And, worse, you going to have to setup a piece of code for each
control that you need to enable and disable. Remember, the ribbon does NOT
store the state of your enabled/disabled button.

It was easy before, since if you enabled, or displayed a control in a menu
bar, you could, or change that value at will. In the case of a ribbon, your
code must hold these values in variables (which is why I love my code
example...since you don't have to write ANY additional code, but use the
above familiar syntax.
As previously stated I have tried to maintain the Ribbon references in a
custom Collection which is loaded via the Ribbon "OnLoad" event. However,
I
am having problems with:

1) Event timing
2) Populating and Accessing the reference in the Collection

Yes. I just wrote that code last night.

A few got ya's that I just learned:

The ribbon load event only fires once, and that is when the form loads.
Additional loads of the form does NOT fire the load event. so, I do need a
global collection of ribbons, and further, I need a routine that checks if
the ribbon is already loaded.

in a standard code module, I have for the global enabled, and disabled, I
have

Public Sub MyVisible(control As IRibbonControl, _
ByRef visible As Variant)
Dim f As Form

If Forms.Count > 0 Then
Set f = Screen.ActiveForm
visible = f.MyRibbon.MyControls(control.ID).visible
End If

End Sub


Public Sub MyEnable(control As IRibbonControl, _
ByRef visible As Variant)
Dim f As Form

Set f = Screen.ActiveForm

visible = f.MyRibbon.MyControls(control.ID).Enabled


End Sub

In addtion, my global load code in this same module is:

Public Sub SetMyRib(frm As Form, Optional strRibbonName As String = "")

Set f = frm

If strRibbonName = "" Then
strRibbonName = f.name
End If

f.RibbonName = strRibbonName
' the above code causes the ribbom to load, and fires the code below
End Sub


Public Sub MyRibbonLoad(ByRef nribbonUI As Office.IRibbonUI)

Set f.MyRibbon.m_ribbon = nribbonUI
Set f = Nothing

End Sub

In the above, I should be saving the ribbon into a global collection in
addition to the "myRibbom"

MyRibbom is a class object I built in code, and every form you make is going
to define this. So, in each form that needs a custom ribbbom, I go:

Option Compare Database
Option Explicit

Public MyRibbon As New clsRibbon


I leave the ribbon setting in he "other" tab blank, because I need to pass
the form name (the problem of screen focus).
(so, the callig code just sets the ribbom value for the form...it less
code).

So in my forms on-load event, I go:

Call SetMyRib(Me, "test1")

me is the form, and "test1" is the name of the ribbom I want to load

then, I have a class module for the robbom, it simply a colleciton of
contorls that gets auto built for me
clsRibbon

Option Compare Database
Option Explicit

Dim colControls As New Collection
Public m_ribbon As IRibbonUI


Public Property Get MyControls(strC As String) As clsRibContorl

Dim i As Integer
Dim intGotOne As Integer
Dim NewControl As New clsRibContorl

' look for contorl in colleciton, if not in, then add...
For i = 1 To colControls.Count
If strC = colControls(i).name Then
intGotOne = i
Exit For
End If
Next i

If intGotOne = 0 Then
' add contorl, set defaults
NewControl.Enabled = True
NewControl.visible = True
NewControl.Label = ""
NewControl.name = strC
colControls.Add NewControl, strC

Set MyControls = NewControl
Else

Set MyControls = colControls(intGotOne)
Me.m_ribbon.InvalidateControl (strC)
End If


End Property


And, for each member of the control collection, we do need to "save" those
values, so, I built a custom class control
to STORE values for each ribbom contorl (since the robbion don't store
them).

it is:
clsRibContorl

Option Compare Database
Option Explicit

Dim m_enabled As Boolean
Dim m_visible As Boolean
Dim m_Label As String
Dim m_name As String



Public Property Let Enabled(bol As Boolean)

m_enabled = bol

End Property

Public Property Get Enabled() As Boolean

Enabled = m_enabled

End Property

'-----------

Public Property Let visible(bol As Boolean)

m_visible = bol

End Property

Public Property Get visible() As Boolean

visible = m_visible

End Property

Public Property Let Label(str As String)

m_Label = str

End Property

Public Property Get Label() As String

m_Label = m_Label

End Property

Public Property Get name() As String

name = m_name

End Property

Public Property Let name(str As String)

m_name = str

End Property



That is it. The whole thing thus gives me a re-usable collection that is
auto populated.

You have to add the enabled, and visible call-backs to ONLY THOSE CONTROLS
that you need this feature:

eg:

<button id="button1" label="Buttion1"
getVisible="MyVisible"
getEnabled="MyEnable"
onAction="=MyTest1()"/>

<button id="button2" label="button2" onAction="=Mytest2()"/>

In the above, you can see that for buttion2, I never enable, or disable the
button, so I did NOT add the call-backs for the getvisble and get enabled.

And, I suppose you could always just include the above two call-backs for
all controls, but it will load up the custom collection.

The above is not a lot of code, as is something I threw together last night
in a very short time (I am just leaning this stuff too, and this is my first
try at this ribbon thing).

So, each form will have it own custom "ribbon" collection with the enable,
visible, and label property able to be set. While this means that you have
to declare that "myrobbon" for each form you use, it only one var and the
rest of the code will make this whole process automatic.

As mentioned, in testing,, the code loads, and runs....however, when you
un-load a form, and then re-load the form, it breaks the code because the
ribbon load event does not re-fire (this is good from a performance point of
view, but since the ribbon does NOT re-fire, I lost the reference to the
internal ribbon that is already loaded. So, either I build a collector for
this, or find a command to force the ribbon to re-fire the load event....

Really, less then 1 hour more on this...and it should be ready to go...

(however, I not have that 1 hour until about Monday!).
 
G

Guy

Albert,

Thank you once again for your comprehensive response. Lots of food for
thought... Good work.

I was aware of many of the gotcha’s eg. the ribbon load only occurs once and
you never know which ribbon is going to get loaded first because that is
dependant on user action ie. Which form they open.

While I digest your posting… I was also thinking about how you could reduce
the amount of form Ribbons perhaps by using the form activate and deactivate
events.

The activate event would customise the Ribbon for that form and the
deactivate event would remove the customisations, ensuring that the “1â€
Ribbon was always left in a consistant state from which the new “active†form
could apply it changes.

However, I haven’t put any of my thoughts into code yet so I don’t know how
feasible this is.

Still plenty to work to do so I had best get on with it.

Your assistance much appreciated and I’m sure that most developers who read
this thread will have learnt something new.

Kind Regards,
Guy
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top