Many years ago I started using b2evolution for my blog even though it was not the most popular platform. I'm still using it. Although it is complex, it gives me options that don't exist in other platforms. Now that it has expanded to include other types of collections (photo blogs, manuals, etc), it is even more complex. Unfortunately, the image insertion routines in b2evolution are quite inflexible, and I suspect that is one reason it will never be very popular.
The guide says you should attach the image where you want it, but it doesn't include the file in the page markup and instead includes a tag that identifies the image. The logic presented in the on-line forums is that you can adjust the stylesheet and php to apply uniformly to all images. You can put the image in different sections (teaser, cover, in-line) of the post and it supposedly is more efficient that way.
Although you have multiple options for placement (vertically) in a post, if it's an in-line image you can't see it in the editor. Instead you see . Additionally you can't modify the formatting of the image. That's where the problems resides. If b2evolution included the code in-line (like it used to) I could modify the HTML/CSS to format the image.
Emulating a print publication, I like to wrap text around some images, and like adjusting the size to make it look more balanced. Well, there is a way to include the markup, but it's not intuitive.
I've discovered after inserting all my images that there is a viable workaround to get the code into the post, something that used to be an option in the editor in past versions.
1. Go to the b2e Back-office and select Files > Browse and upload the images for the post. I prefer to put them in the root collection folder, not the quick-uploads.
2. From the post, attach any image to the post (ideally an inline). The easiest way is to select Attach existing files and select one. You won't be including that in the post, but you will be using that image to get to other files you would like to insert. If an image tag is inserted into the post you can delete it if you want.
3. Position your cursor at the point in your post where you want to include one of your uploaded images.
4. Go to the attached image in the Images and Attachments section and click on the Locate file icon (bull's eye).
5. When the Attach files dialog box is displayed find the file you really want. You may have to change folders to find it. Again, I prefer to use images in the root collection folder.
6. Check the box for the file to select it. Don't press Attach.
7. Finally, at the bottom, with selected files: change the option to Insert IMG/link into post and press Go.
The main reason I want the code in the post is so I can customize the image. In order to do that switch to Markup view and find the image's code. It'll begin with something like
<div class="image_block"><a href="..">
Two of the modifications that I generally make are to position the image and resize the image to look good in the post. By positioning, I generally float it left or right.
In markup, find the image markup as noted above and change the div to add
style="float:right"
so it looks like this
<div class="image_block" style="float:right"><a href="..">
If you are floating, it might also be necessary to adjust the margins by changing it to
style="float:right; margin-left:20px"
The image should show the actual size in the img tag and would look something like this.
width="431" height="56" />
If the size is not appropriate for the post, I simply change the width to a percentage and drop the height so it would look something like this
width="35" />
I have used b2evolution for many years, and find it useful for multiple blogs, but I guess these kinds of challenges are one reason it is not so popular as a blogging platform.
]]>Another significant difference is the use of inherited forms. While users should not care, it does mean the code is smaller and (theoretically) faster. For me, it means changes can be made to the interface easier.
Finally, the new Hoot includes a ribbon, like you might have seen in Microsoft Office applications beginning in 2007. While this ribbon is not from Microsoft, it is a very good look-alike. All of this may sound boring, but for a developer it is a little fascinating, figuring out how things work, as well as how to work around limitations.
With the MDI I could avoid creating new form for each call and instead open them as child windows. Other than setting the appropriate flags on the parent form, the only thing I had to do different is open the forms appropriately for the MDI. One standard way to open such forms is with code like what I've used. This also cascades the new form over the last active child.
private void OpenChild(Form child)
{
child.MdiParent = this;
Form last = new Form();
if (this.MdiChildren.Count() > 0)
foreach (Form f in this.MdiChildren)
if (f == this.ActiveMdiChild)
{
last = f;
child.Location = new Point(last.Location.X + 25, last.Location.Y + 25);
break;
}
child.Show();
}
If I want to insure only one copy of that form is used I simply check for the form name among the children.
private void OpenOneChild(Form child)
{
if (!(FindChild(child.Name) == null))
{
child.Activate();
return;
}
OpenChild(child);
}private Form FindChild(string name)
{
foreach (Form f in this.MdiChildren)
{
if ((string)f.Text == name)
return (f);
}
return null;
}
Of course, if I wanted to open a form from a child window, I would use a different method.
private void OpenSibling(Form child)
{
child.MdiParent = this.ParentForm;
Form last = new Form();
if (this.ParentForm.MdiChildren.Count() > 0)
foreach (Form f in this.ParentForm.MdiChildren)
if (f == this.ParentForm.ActiveMdiChild)
{
last = f;
child.Location = new Point(last.Location.X + 25, last.Location.Y + 25);
break;
}
child.Show();
}
When I mentioned Inherited forms on one discussion board one of the responses started with "ouch, ouch, ouch". Well, it's not so bad. With inheritance in C# you can't normally pass parameters to an inherited form, so I found a workaround. While inherited forms apparently can't take parameters in their constructors, at least not directly, you can access the controls of the inherited form and force a parameter.
1. Add a label named lblTrigger (public or protected)
2. Add an event to the label for the TextChanged event
private void lblTrigger_TextChanged(object sender, EventArgs e)
{
switch (lblTrigger.Text)
{
case "Hooks": ShowHooks(); break;
case "Words": HideHooks(); break;
case "Slides": btnSlideShow_Click(sender, e); break;
case "ChangeLanguage": PopulateResources(); break;
}
lblTrigger.Text = "Trigger";
}
3. Change that label from the ribbon or another form
public static void SetTrigger(string text, System.Windows.Forms.Form frm) {
if (frm == null)
return;
foreach (System.Windows.Forms.Control ctl in frm.Controls)
{
if (ctl.Name.Equals("lblTrigger"))
ctl.Text = text;
}
}
You're not limited to one label, either. You can add parameters to use with a different label. That's what I've done using lblParameters included in one of the code segments below. Set the parameters, then "pull" the trigger.
In one case "SavedSearch" is the text I set for the trigger.
case "SavedSearch": OpenFromRecent(); break;
That then calls this method which simulates a button click on the form.
private void OpenFromRecent()
{
specs = Utilities.LoadSearch(lblParameters.Text);
if (specs == null)
return;
cboSearchType.SelectedIndex = Array.IndexOf(english, specs.searchtype);
if (!(cboSearchType.SelectedIndex == -1))
{
txtSearch.Text = specs.letters;
cboMin.SelectedIndex = specs.minLen - 1;
cboMax.SelectedIndex = specs.maxLen - 1;
cboBegin.Text = specs.prefix;
cboEnd.Text = specs.suffix;
txtFilter.Text = specs.filter;
}
btnSearch_Click(this, EventArgs.Empty);
}
The ribbon I am using in this project is the one mentioned in the Code Project article at https://www.codeproject.com/articles/364272/easily-add-a-ribbon-into-a-winforms-application-cs. It's a third-party ribbon for C#, not WPF or other Microsoft. While it looks and performs well, there is a limited documentation for it. The intellisense and integration with Windows is good, and many items are similar. One of my biggest challenges was determining how to use the Recent Items list.
One thing that was not so easy to understand was the use of the Recent Items list in the Orb menu. Determining how to create recent items dynamically, however, was one of the biggest accomplishments recently. My last blog entry touched on recent items and using the C# .Designer file to figure out how to create them. Actual implementation was a little more difficult. With my implementation I created a new class with properties of the recent item that I would use.
public RecentItem(string tag, string text)
{
ItemID = 0;
ItemTag = tag;
ItemText = text;
UserID = Properties.Settings.Default.User;
}
The text is the filename. The tag indicates the type of item since the items are not identified by file extension. ItemID will be updated when saved to the database. The database fields are similar to the class fields.
..
List<RecentItem> load = new List<RecentItem>();
load = DBManager.LoadRecentItems();
AddRecentItems(load); // see below
ribbonMain.OrbDropDown.RecentItems.Reverse();
..
Reverse is used to put latest items at the top. The ID is set by the database using autonumber so the oldest items have the lowest ID and are loaded first.
private void AddRecentItems(List<RecentItem> recentItems) {
foreach (RecentItem item in recentItems)
DBManager.OrbAddRecentItem(item); // see below
}
public static void OrbAddRecentItem(RecentItem recent)
{
frmHootGold top = (frmHootGold)System.Windows.Forms.Application.OpenForms["frmHootGold"];
RibbonOrbRecentItem adder = new RibbonOrbRecentItem();
adder.Tag = recent.ItemTag;
adder.Text = recent.ItemText;
adder.Value = recent.ItemID.ToString();
top.ribbonMain.OrbDropDown.RecentItems.Add(adder);
adder.Click += new System.EventHandler(top.OrbRecent_Click); // see below
}
When the items are added to the list, either as new items or when loaded from the database, the click event is set.
A separate method is used when actually adding a new item that has one difference to add the new item to the list that has already been sorted in descending order.
public static void OrbInsertRecentItem(RecentItem recent)
..
top.ribbonMain.OrbDropDown.RecentItems.Insert(0, adder);
That's done from one of the child windows with code segment something like this
RecentItem adder = new RecentItem("SavedSearch", searchFile);
DBManager.AddRecentItem(adder);
DBManager.OrbInsertRecentItem(adder);
When a user clicks an item on the Recent list, the following method uses the trigger and parameters mentioned earlier to pass that information to a new child form.
public void OrbRecent_Click(object sender, EventArgs e)
{
RibbonOrbRecentItem item = (RibbonOrbRecentItem)sender;
Form form = new Form();
switch (item.Tag.ToString())
{
case "SavedSearch": form = new frmCombo(); break;
case "Textfile": form = new frmTextfiles(); break;
}
OpenChild(form);
MDIUtils.SetParms(item.Text, form);
MDIUtils.SetTrigger((string)item.Tag, form);
}
One of the things I learned about Recent Items is that there is no obvious way to enable user-deletion of items. I notice Word doesn't have that either, nor does Windows, so I decided not to worry about it.
If you use this in a program you may notice that the items are added to Windows list of Recent Items, but it's not because of this method. It's simply because they were files that "you" opened. Anyway, this is my contribution to the C# Ribbon knowledge base.
]]>When VS creates an item, it does it with code. Finding how it's done may be as simple as looking at the code behind, i.e. in the form's .Designer file. Specifically, it answers the common questions:
What's the name of the class you use to create the item?
What's the name of the parent class, and how do you add an item to it?
Here's a couple of examples:
Example 1: I have a form that inherits from another form. The base form includes a complex context menu so derived forms do not have to duplicate that work repeatedly. But there are a couple of forms that need some additional items added to the list. How can I dynamically add items to that context menu for some forms?
In order to see what Designer does, add the items to a copy of the base form in design mode (or look at a previously added item). In my case, there were items name popLoadSearch, popSaveSearch, and a separator.
Then, open the .Designer file and search for the names. Each was found four times. You can verify some of the statements by looking at the properties of the previously added items. The first set were statements that create the objects and the rest configured the objects and added them to the context menu collection. This is what I found.
this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
this.popLoadSearch = new System.Windows.Forms.ToolStripMenuItem();
this.popSaveSearch = new System.Windows.Forms.ToolStripMenuItem();
//
// toolStripSeparator1
//
this.toolStripSeparator1.Name = "toolStripSeparator1";
this.toolStripSeparator1.Size = new System.Drawing.Size(162, 6);
//
// popLoadSearch
//
this.popLoadSearch.Name = "popLoadSearch";
this.popLoadSearch.Size = new System.Drawing.Size(165, 22);
this.popLoadSearch.Text = "Load Search";
this.popLoadSearch.Click += new System.EventHandler(this.popLoadSearch_Click);
//
// popSaveSearch
//
this.popSaveSearch.Name = "popSaveSearch";
this.popSaveSearch.Size = new System.Drawing.Size(165, 22);
this.popSaveSearch.Text = "Save Search";
this.popSaveSearch.Click += new System.EventHandler(this.popSaveSearch_Click);
this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.popLoadSearch,
this.popSaveSearch});
private System.Windows.Forms.ToolStripMenuItem popLoadSearch;
private System.Windows.Forms.ToolStripMenuItem popSaveSearch;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator2;
Yes, this looks backward since variables have to be declared before they are used but in Designer the commands are contained in the InitializeComponent method, while the declarations are in the class. Just remember to move the declarations to the top, and preferably instantiate them at the same time. I also noticed the command to size the menustrip, so I figure I needed to change that too.
this.contextMenuStrip1.Size = new System.Drawing.Size(166, 484);
After removing some reference terms (this., private, Systems.Windows.Forms.), renaming the separator, and compressing some of the commands, this is what my addition looks like. I include a command in the form load method to execute this.
private void AddContextItems()
{
// Declare Variables
ToolStripSeparator topSeparator = new ToolStripSeparator();
ToolStripMenuItem popLoadSearch = new ToolStripMenuItem();
ToolStripMenuItem popSaveSearch = new ToolStripMenuItem();
// Configure objects
topSeparator.Name = "Separator";
topSeparator.Size = new System.Drawing.Size(162, 6);
// popLoadSearch
popLoadSearch.Name = "popLoadSearch";
popLoadSearch.Size = new System.Drawing.Size(165, 22);
popLoadSearch.Text = "Load Search";
popLoadSearch.Click += new System.EventHandler(popLoadSearch_Click);
// popSaveSearch
popSaveSearch.Name = "popSaveSearch";
popSaveSearch.Size = new System.Drawing.Size(165, 22);
popSaveSearch.Text = "Save Search";
popSaveSearch.Click += new System.EventHandler(popSaveSearch_Click);
//add objects to collection
this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
topSeparator,
popLoadSearch,
popSaveSearch});
// resize the menu
this.contextMenuStrip1.Size = new System.Drawing.Size(166, 534); // 484 + 22 + 22 + 6
}
Of course, I also had to create the methods assigned to the Click method of each.
Example 2: For my program Hoot I'm adding a Ribbon wrapper for C# WinForms and I need to dynamically add new recent items to the Orb menu. You can read about this Ribbon at https://www.codeproject.com/articles/364272/easily-add-a-ribbon-into-a-winforms-application-cs. Other than the basic introduction for getting started there's not much documentation since it's not officially Microsoft (like WPF), so I have to figure things out. The Orb menu includes a list of items that are commonly used in other programs to list recent items opened. I want to do that but don't know the classes or methods to use. Intellisense is not always the easiest way to figure that out, so I create an item in design mode called OrbRecent and look for it in the Designer code.
this.OrbRecent = new System.Windows.Forms.RibbonOrbRecentItem();
this.ribbonMain.OrbDropDown.RecentItems.Add(this.OrbRecent);
//
// OrbRecent
//
this.OrbRecent.Image = ((System.Drawing.Image)(resources.GetObject
("OrbRecent.Image")));
this.OrbRecent.SmallImage = ((System.Drawing.Image)(resources.GetObject
("OrbRecent.SmallImage")));
this.OrbRecent.Text = "Recent";
private System.Windows.Forms.RibbonOrbRecentItem OrbRecent;
Now that I know how VS created and added the item to the collection, I can create a method to do that dynamically, and adjust it to my future needs. I will add it to my Utilities class and use a database table to store the entries. Of course, I'll have to reverse sort the entries so the most recent are on top, and to keep the list at a reasonable size I will need to monitor the size and remove old items.
AddRecent(string spec) {
RibbonOrbRecentItem adder = new RibbonOrbRecentItem();
adder.Text = spec;
this.OrbRecent.Image = ((System.Drawing.Image)(resources.GetObject
("OrbRecent.Image")));
this.OrbRecent.SmallImage = ((System.Drawing.Image)(resources.GetObject
("OrbRecent.SmallImage")));
ribbonMain.OrbDropDown.RecentItems.Add(spec);
}
Visual Studio's Designer code taught me that.
]]>This example combines the use of combo boxes, a separate DataSource, and localized string
s for alternate cultures.
Using a combo box, programmers can use a selected item as a variable or parameter within a single method. In other cases, the combo box is a collection of options, each of which executes a different method. The combo box can be populated within the design, or assigned using a DataSource
. Even a simple string
array can be used for that. Processing the selection from the combo box when executing a different method for each combo box entry can be done using the index of the selected item.
string[] special = { "Select", "Vowels", "DoubleI",
"DoubleU", "NoVowels", "Consonants", "QNotU",
"Palins", "HighFives", "HighFours", "NoHooks"};
cboSearch.DataSource = special;
switch (cboSearch.SelectedIndex) {
case 1: btnVowelHeavy_Click(sender, e); break;
...
case 5: btnConsonantDumps_Click(sender, e); break;
...
}
When using the combo box selected index the routine may have to be modified each time a new item is added or items are moved around. If we add an item to the middle of the list, we have to figure out what the item number will be and then change the select
statement. Case 2 may be the new one, so the original case 2 has to be changed to case 3, case 3 to case 4, etc.
To avoid that, it is possible to use the selected item's content. Using the actual string
in the collection is often a clearer way to code.
switch(cboSearch.SelectedText) {
case "Vowels": ...
Thus, case 1 would be replaced with case "Vowels
". Additions to the collection will not require any reordering.
This works well until you have to support localization, that is, present different collections of string
s based on the language selected. The string
used in a switch
statement must be a constant, so you can't use:
switch (cboSearch.SelectedText) {
case rm.GetString("Vowels"): ...
We could go back to using indexes, but we want to avoid going back to that. This is where the CultureCombo
comes in handy. This simple class and the supporting method uses the characteristics of a combo box to create localized strings, while keeping the ability to process the selection using string
identification.
class CultureCombo
{
public string name {get; set;}
public string display {get; set;}
}
Next, you would incorporate the method that processes the array. Using this code, you can then apply that to the combo box.
private List<CultureCombo> comboList(string[] termList)
{
List<CultureCombo> options = new List<CultureCombo>();
foreach (string word in termList)
options.Add(new CultureCombo { name = word, display = rm.GetString(word) });
return options;
}
The code assumes you have created a ResourceManager
called rm
. Replace rm
above with whatever variable you used for the ResourceManager
. In my code, it is represented with the code.
Assembly myAssembly = typeof(Utilities).Assembly;
ResourceManager rm = new ResourceManager("Hoot.Resources.Strings", myAssembly)
The first step to using this in a program is to create an array of string
IDs, the string
used to access the culture's translation. If you have incorporated localized string
s, you would already have these. In my example:
string[] special = { "Select", "Vowels",
"DoubleI", "DoubleU", "NoVowels", "Consonants",
"QNotU", "Palins",
"HighFives", "HighFours", "NoHooks"};
Next, use the method to populate the combo box.
searchOptions = comboList(special);
cboSearch.ValueMember = "name";
cboSearch.DisplayMember = "display";
cboSearch.DataSource = searchOptions;
Finally, find the item in the list by looking at the value
(or "name
") of the combo box item:
switch (cboSearch.SelectedValue.ToString())
{
case "Vowels":
btnVowelHeavy_Click(sender, e);
break;
case "Consonants":
btnConsonantDumps_Click(sender, e);
break;
..
}
While the string
presented in the combo box could be "Vowels
", or "Voyelles
" depending on the language, the value is the constant "Vowels
".
Combo boxes have a DataSource
field, so you can use many different sources to populate the box. They also have separate fields for identifying the option chosen. You can use the SelectedIndex
, as well as the SelectedText
or SelectedValue
. You determine what each represents by modifying the DisplayMember
or ValueMember
field.
List boxes are very similar having the same DataSource
, DisplayMember
, and ValueMember
. You can also change each of them programmatically.
This code is from the program Hoot, a word game study tool I created for Scrabble and Words with Friends players.
]]>The latest addition to Hoot is the combination search screen. With this screen you can select one of several searches and then filter the search based on other options. This was inspired in part by the app Scrabble Expert Pro, and expanded to include the filters.
The searches include Letter Count, Contains, Contains All, Prefix, Suffix, Anagrams, Sub-anagrams, Blank Anagrams, Super Anagrams, Extensions, Subwords, Hooks, Anagram Hooks, and a Pattern search. In comparing with other search tools, the terminology can be confusing. For example, the Word Builder in Scrabble Expert is the same as sub-anagrams where only words consisting of some letters in the search box are shown. The Contains All Letters is the same as super anagrams where all of the letters are used along with other letters.
The filters include minimum and maximum word length, prefixes, suffixes, and a filter rack of available letters. After doing a primary search, you can continually edit and apply the rack filter to the primary results more quickly. Like most Hoot search screens, the list has a context menu (with the addition of definitions, if installed). There is also a convenient button to clear everything, and another beside it to open a new window of the same ilk.
Although not the MOST useful feature, subject lists in Hoot are probably the most unique. Previously I called them named searches. Many players and clubs publish lists of words on a given subject for many different lexicons and publish them as text files, webpages, or PDF downloads. The hassle there is using lists from different lexicons, and finding the lists for your choice lexicon. Hoot makes that easy by enabling you to store lists in the database and studying those lists without worrying about the lexicon. Hoot only shows the words in the list that are valid in the selected lexicon.
For example, you could use the list of bread terms at http://www.absp.org.uk/words/foodbread.shtml for the CSW15 list, and when viewing OWL3, you only see valid words in that one. In CSW15 there are about 110 valid words. In OWL3, only about 80 of them are valid, and in Words with Friends there's only 60 good words.
Of course, one of the features of the Subject list editor is that you can add other words later. I added several, and viewing the slide show I saw more words and variations that could have been added, including WHITE, WHEAT, RYE, GINGERBREAD, READBASKET, CRISPBREADS, BREADFRUIT
SHORTBREAD, SWEETBREAD, SHOWBREAD, plus the plural of most words.
Slide shows are not only useful, the impetous for choosing to develop Hoot was the lack of slide shows in current tools resembling Lexpert's slide shows. With the slide show you can study this or some other list, one word at a time, on a timer, or not. You can also study each word along with some related words, such as hooks, anagrams, and extensions. So while I am studying the term RYE, I notice that you can make it plural, or extend it both ways to form CRYER, DRYER, FRYER, PRYER, WRYER, and pluralize and extend some of those words.
There is standard navigation through the list, four sorting options, and the ability to jump to any word. You can also flag words that you already know so you don't have to see them every time you study a certain list. An alternate list display shows the hooks for the subsearch. And finally, there is a tiny slide show that shows only the word in a small, resizable, window with the same navigation and sorting options. Of course, the slide show is available in all searches, not just subject lists.
Of course, subject lists, related words, and other features of a study tool assume you are using the same language and locale. The same word may have two different meanings in different languages and words in different languages are modified differently. Speaking of which, Hoot now has a French language option. The search features don't support French pecularities but you can add Subject lists for French categories, or change the prefixes and suffixes to support French word study. If you're playing in French you may want to download the ODS lexicon or use a later version. Hoot can actually support both English and French using different databases. You can save the ODS lexicon in one database with the modified prefixes and suffuxes, and use the original English in another.
Incidentally, I'm not a French speaker, so the translations may not be the best. Many of them were generated from the Visual Studio tool I often use. You might get a laugh out of some of them, but afterward let me know how terms would best be translated.
One of the most recent additions to Hoot is definitions for words up to about 10 characters. As with the initial development of Lexicon creation the function is limited. The definitions are add only, so you can't delete or edit them without opening it with Microsoft Access. I've incorporated the definitions in two places so far, accompanying the slide show, and in the context menu in Combination Searches. The definitions were generously provided by Mike Wolfberg, the author of WHAT.
As is often the case, the development of Hoot required me to go into areas beyond the actual coding. In addition to the webpages for the program, and the help file I developed independently, I also had to design a font to be used with the program. I decided on a font instead of using tile images because they are much easier to resize, and otherwise manipulate. I started using Scramble as the tile font until I discovered it had to be licensed. NuTiles was developed using the outline of a different font available for free use. I simply resized the images and combined it with a background.The latest version of the font displays the characters using uppercase, and provides a blank tile when using a question mark. The tile values are those of ScrabbleTM.
NuTiles can also be used in the text reproduction of tile boards. Simple type the letters, or '?', and color the tile appropriately. Future versions should add DL, DW, TL, and TW for the bonus tiles. This is without spacing. You can add line-spacing or character spacing as desired. It is available as a separate download. Like most fonts, it is only available in a single color format. You select the color (any color) to use and the letter and numbers in the font use the background color.
Don't be mislead. All of these are just highlights and new features. There are many other features of Hoot that might interest you. Read about Hoot in the on-line help pages. The very latest features (like the Combination search) may not be discussed yet, but they will eventually make it to the help file. And, yes, Hoot is freely available to download and share.
]]>Note: This is an IT article, not a tax article.
As a part of a campaign to increase awareness of education tax credits I created an AOTC worksheet. The Excel version of the worksheet is demonstrated in my article Amending for Education Credits and includes the simple formulas being used. There is also a PDF version of the spreadsheet that I’ve made available for download and it calculates the values based on the four entries you make.
Creating such a worksheet begins with a simple export of a Word document to PDF. Adding the calculation logic of the worksheet requires a little more work and a full version of Adobe Acrobat. Using it only requires the Acrobat Reader.
The process used to tap into a PDF’s intelligence can be used to create other files that can help clients, users, members, or staff do similar calculations without the need to have or use spreadsheet software. Of course, it is not an Excel replacement, but there are cases, as here, where it can be quite handy. Acrobat has many other abilities that could be integrated in the workflow of a professional office as well.
The first thing that I do is create a document and add the table that calculates the AOTC. I started this in Word and then printed to a PDF file. You may also be able to save or export to a PDF. You should make sure the formatting and alignment is the way you want it because it’s difficult to impossible to make major changes in a PDF. Modifying the form for user form-filling will require you to set fields and add calculations. There are 12 fields in this worksheet but the form has only four fields the user needs to enter information into and they have been highlighted with a wider border.
If the PDF doesn’t open automatically, open it with Adobe Acrobat.
With the PDF open, select Forms on the menu and then Add or Edit Fields…
Acrobat makes it easy by offering to detect possible form fields in the document. You can manually add other form fields if you need to. Select Yes.
With fields detected and added the form will look like this.
Once all the fields are added, you could allow users to make all their own calculations. The instructions are there but the purpose of this guide is to show how to let Acrobat make the calculations for the user. For that you will need to name the fields and add code to make the calculations.
The name of each field comes from the adjoining text. A few fields may be added that will not be used, such as the Line 4 Taxable Scholarships. Clicking on any field will make it active and highlighted in blue. You can then delete or edit the field properties. For the Taxable Scholarships field in the left column I simply click and delete. Then I rename the other fields.
Right click on the field and select the menu option to Rename fields, and enter a short meaningful name for the field. Do that for all fields to be used.
The following shows all fields renamed for my form.
You can also right click and edit properties. From this menu you can rename the field, add any tooltips and set other properties. For the line 10 properties, I’ve added the tooltip text shown. It is best to rename all the fields before going further since the formulas will use the names assigned.
After all the fields have been renamed, for each field, go to the properties again (right click). Select the end tab labeled Calculate. This is where you enter the formulas needed to make the calculations. All of the formulas in this form use a custom calculation script in the Javascript language. Javascript is a popular language for web pages. The scripts here are basic with limited syntax. With a little study you should be able to determine how to assign variables, add, subtract, and use conditionals (if statements).
In order to edit the script, click the Edit button to the right of the selected box. Following is the script for line 10 (AOTCQualExp). The script gets the value from line 1 and then determines the appropriate value for this line. In pseudocode this says
If the value in line 1 (field named QualExp) is greater than 4000, then the value in this field is 4000, otherwise this value is the same as the value of QualExp.
When you have finished adding all the scripts you can see them in a single document from the Advanced menu in Acrobat. Select document processing and then Edit All Javascripts.
Remember that the user only enters values in four of the fields in this form, with the rest being self-calculating. For fields that do not require user input, set the Read-Only attribute. This is done by checking the Read-Only box in the first tab under Properties.
In order to simplify the use of the form you can also add a button to clear the form. To do this, go to
Save!
The only thing left to do now is test and re-test the form calculations and make corrections where needed. When you are pleased with your work, save, backup, and distribute the form.
]]>As in AZZ, you can also create external data files of cards in Simple Cards. The external files are actually separate databases. Ignoring the general development of Simple Cards for now, this article shows how to enable external table data in an Access program, first with an explanation and then some code samples.
When Access displays a form, it relies on a source file for the data, either directly from a table, or from a query based on one or more tables. Typically, the only time Access uses an external table is if the database is split into front end/back end, but it is possible to access external data as a record source for a form or report, without technically splitting the database and creating links to external tables. The best way to do that is to use a query instead of a table as record source for a form.
If the form is based on a query the tables are normally from within the database. However, if you open the query for editing and access the properties of the query, you can see that there is an option to specify the database to use under Source Database. If the query was created with internal tables, it will have "(current)" in gray. If you change that to refer to a different database, it will load data from that external file instead. After specifying an external database, you can view the SQL code for the query and notice that an additional qualifier, 'IN', is used in the query with the file path of the external file.
In my project, however, I wanted the user to be able to optionally access one of several different databases for the form. So the user doesn't have to go to the query and enter a long file path to do this, Application.FileDialog is used to select the database. The problem is, outside of Microsoft, there is no direct way to change the Source Database of the query. I've searched, and asked, and searched, and I cannot find a way to access those properties. In order to change that, you have to change the SQL property of the query. To load an external file, you have to build the SQL statement; and to read the filespec of a loaded file, you have to search the SQL statement.
In developing Simple Cards I also had to incorporate some user messages so the user would know what file is being used. In addition, I change the caption to show the file being used if it's an external file, both when loading external files, and when restarting with an external file. Finally, in order to make this work with less confusion for the user, instead of saving external databases with the normal extension, I used the "crd" extension. Access doesn't care, and the files won't be associated with Access.
Following are the steps I took to enable this feature.
First, I create the form to open the data using a query (qryCardFile) so I can change the query in order to change data source. On the main form is a button that enables the user to open an external file, with the following code to change the SQL statement.
Notice that I change the query by adding IN and the name of the file selected. You will also notice that I use an extra parameter (FileSelected) in the OpenForm command. The extra parameter is OpenArgs which enables you to pass the filespec to the form open subroutine. And, of course, you notice that I use this to open a different form. Using form frmReopen is a tidy way to close and reopen the main form without confusion for the user.
Following is the relevant code in frmReopen
This simply closes the main form and then reopens it with the new external data table. The change in the query's SQL in the prior code sample is what enables the loading of the external data. The Me.OpenArgs parameter is only used to update the form caption.
Incidentally, this technique could be used to view the content of any external table since the query can use the global qualifier, although additional work is required to get a list of tables in that external database.
The following code is what actually changes the caption
The following code (GetSourceDB) is what gets caption information from the SQL statement of the query
If this code cannot find 'IN' in the query, then the query must be using the internal table.
Of course, the user will not want to manually create external databases, so an option to create one is made available from a button on the form. This simple code snippet shows how to create a database and copy the structure of the CardFile table to that database. Then it uses a modified version of the ChangeCards subroutine to load that blank external file.
While the whole concept of using external files may be hard to grasp at first, with a logical study of the flow of data and order of instructions in the code it's possible to create routines to do things like this and make it look simple.
Note that you may have to set the relevant Office references for your version of Access in order to use the FileDialog routines. Some word-wrapping may have occurred in code sections presented but marking and copying usually preserves underlying formatting.
P. S.
One issue with using external database files in an application is that Microsoft Access processes queries before processing forms. As a result, if the query fails to load, the form fails to load. This could be due to an invalid card file, or a missing card file. In order to avoid this unpleasant situation, you have to error check (not shown in the code clippings above) for the file when loading the form. You can’t error check in the main form itself. In this case, the check is done in the Splash screen, or Reopen form, when it opens the main form.
]]>