Monday, September 28, 2009

db_query and LIKE %s

You need to use %% to create a literal %.

Example:

db_query('select x from {table} where x like "%%%s%"', $string);

Another way
nevets - October 9, 2006 - 03:56

If you define the thing you are search for like
$pattern = '%' . $somevalue . '%';

and do your query something like
$results = db_query("SELECT .... WHERE field LIKE '%s'", $pattern);

you get rid of the need for the pairs of % signs.

The White Screen of Death (Completely Blank Page)

The White Screen of Death (Completely Blank Page)

* View
* Edit
* Revisions

Developers and coders · Site administrators · Drupal 4.7.x · Drupal 5.x · Drupal 6.x · No known problems
Last modified: September 5, 2009 - 19:47

Occasionally a site user or developer will navigate to a page and suddenly the page content disappears, and is completely blank. No content. No errors. Nothing. This often, but not always, happens after updating a module, theme, or Drupal core. This is what is referred to by most members of the Drupal community as the White Screen of Death or WSOD. There are several reasons why this might occur, and therefore several possible solutions to the issue.

(Note: The suggestions on this page might solve the problem even when you do not get the WSOD as it relates to an Internal Server Error.)
"Invisible" Errors

If error reporting is turned off, you could be getting a fatal error but not seeing it. On a production site, it is common to have error reporting turned off. If that is the case and PHP has hit an unrecoverable error, neither an error nor content will be displayed, therefore you end up with a completely blank page.

What you can do about this is either turn on PHP error reporting so it displays a message on the page itself, or check your log files (from the server) to look for the error. How to do both of these are explained below.
Enable Error Reporting

Although it may be turned off on commercial hosts and production sites (for good reason, so that users do not see the errors), these errors are one of your best tools for troubleshooting. To enable error reporting, temporarily edit your index.php file (normally located in your root directory) directly after the first opening PHP tag (do not edit the actual file info!) to add the following:

<?php

error_reporting(E_ALL);
ini_set('display_errors', TRUE);
ini_set('display_startup_errors', TRUE);

// $Id: index.php,v 1.94 2007/12/26...
?>

You will now be able to see any errors that are occurring directly on the screen. Memory problems may still not be displayed, but it's the first step in a process of elimination.
Log Files

Your log files can be accessed a few different places. This will vary depending on your host, but it's good to know what and where they are.

To access the files directly on the server, on some unix shells (you may need to alter this to suit your environment), you can type the following command:

tail /var/log/apache2/error.log

To check that you are looking at the right file, you may wish to type the following commands to find where the log files are.

grep 'ErrorLog' /etc/apache2/*
grep 'ErrorLog' /etc/apache2/*/*

Otherwise, if you are still able to access your admin pages through your site, which you often can during a WSOD, check the watchdog log for errors. For example you may see the 'headers already sent' error, which relates to the whitespace error (explained in the next section).

The path to your watchdog log, should you lose your admin menu is:

* (Drupal 4.7) http://www.example.com/admin/logs/watchdog
* (Drupal 5) http://www.example.com/admin/logs/watchdog
* (Drupal 6) http://www.example.com/admin/reports/dblog

Your results will vary in different hosting environments, but this is a good starting point.
Whitespace at the End of a PHP File

The most common code error that causes a WSOD is having additional whitespace at the end of a PHP file. To avoid this issue, it is a Drupal coding standard to not include the closing ?> on a PHP file.

You may also have the 'Include Unicode Signature (BOM)' option turned on on your editor, which should be turned off.
PHP4 Syntax Errors and Incompatibility

Some versions of PHP4 gag on some function declaration syntax. Here are examples of syntax that fails:

function media_mover_api_media_mover($op, $action = null, $configuration = null, &$file = array(), $running_config = null ) { ...

function media_mover_api_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) { ...

This appears to be a bug in PHP4 parser that makes it not be able to handle either too many "=" clauses in a function declaration or handle "&$..." So in at least some cases, this is a de facto PHP4 incompatibility.

Solution: Upgrade to PHP5.
PHP Versions

If you were previously running Drupal on a server with multiple versions of PHP you may have had special code in your .htaccess file telling Drupal which version to use. For instance, you may have added AddType x-mapp-php5 .php to your .htaccess file if your hosting provider required it to ensure PHP5 was used rather than PHP4.

If that is the case, remove this line in your .htaccess file.
Invisible Errors for Developers

If you are developing a module, you may first want to test loading your file(s) to make sure that there aren't any obvious PHP syntax errors. This happens because the include_once() or require_once() function calls simply do not always report the errors [if someone knows why and has a fix for that one, please add the info here!]. The following is the command you want to run on your system (it requires PHP console, or CLI, to be installed):

<?php
  php 
?>

This command will ask PHP to parse your file. If you forgot a semi-colon ";", or a closing bracket "}" it will give you the error immediately. Fix it, and try again on your server.

Better yet, you may want to write an automated test to check for such errors. More info about Simpletest here.
Implement Hook Twice

You can also get a blank screen if you have by mistake implemented the same hook more than once. For example, accidentally implementing hook_help twice.
Output Buffering

Some modules need output buffering turned on.

To do this, try adding these lines to your .htaccess file (normally located in your root directory:

php_value output_buffering On
php_value output_handler mb_output_handler
Zend Compatibility Mode

If you get the WSOD while setting up a new server, you may have a problem with zend compatibility being on. If you check the error reporting you may see an error with "Trying to clone an uncloneable object of class mysqli." This is caused by the zend compatibility mode being On in the php.ini file.

To fix this, set zend compatibility to off by editing the applicable line:

; Enable compatibility mode with Zend Engine 1 (PHP 4.x)
zend.ze1_compatibility_mode = Off

More detail about zend compatibility can be found here.
Clearing the Cache Table

Depending on the problem, clearing the cache table (via phpmyadmin for example) can resolve a WSOD.
Ionic Rewriter: WIMP

If you are using Drupal on a WIMP stack and getting the WSOD on the http://www.example.com/install.php page it may be that you cannot have the Ionic Rewriter ISAPI module installed in IIS during the Drupal install.

The solution in this case is to add it in after install is completed.
PHP Memory Limits

Another common reason for the WSOD is issues with memory limit. Traditionally, this has most often been a problem showing up (or rather, not showing up) in the modules admin screen, by it giving a WSOD. This issue has pretty much been solved for that page; however, there are still instances that will occur in other modules (usually showing up during admin actions like bulk updates) where PHP memory can be exhausted.

Try the solutions here first if this issue came up when you tried to go to the module page.

You may also want to try running the update.php script. If you do not know how to run update.php or your user does not have the permissions to run it (and you do not have the user1 login), there is more info about update.php here.

Next, you'll want to confirm that the change has had an effect with a phpinfo() page. If you are hosting the site and it didn't work, check that you were modifying the correct php.ini file (it's named in the phpinfo). If your site is hosted by someone else and you failed to increase the memory limit, then your host has probably locked it down (for good reason) and you'll have to negotiate with them. There may be a few work-arounds to try, like creating a custom php.ini, but it will vary from host to host.
Module and Theme Related Errors

If the error is not originating from a memory limit, or any of the above errors, the error may be coming from bad code in a module or your site's theme.
Non-recommended Module Versions

If you are working with a module that is not in a recommended release version, you may have success by upgrading it to a recommended version or disabling/removing the module. To disable the module, simply go to the module admin page (Administer > Site Building > Modules) and uncheck the checkbox next to the module, then click "Save Configuration."

You can tell whether a module is recommended by looking on the module page, the version will have a green background with a green checkmark at the right, and say eg. "Recommended for 6.x." Particularly if the module is a development version, eg. "6.x-1.x-dev" it may not be recommended, and will then have a red background with an "x" at the right.
Name Clashes

Another possible cause for a blank page is a name clash, i.e. a module and a theme are using the same name. For example, if module "foo" implements hook_block() with foo_block() and there is also a theme "foo", then the theme engine will invoke foo_block() as the theme function to render a block. While foo_block() might not trigger a WSOD, foo_page() will.

No error messages are produced, because this is a wanted behavior of Drupal's theme system.

The solution in most cases is that if either the module or the theme (or both) are custom (created by yourself), rename it.
Character Encoding on template.php

If the error is as follows, particularly if the output started at line 1, it may be that the character encoding on your template.php file is not set correctly.

Cannot modify header information – headers already sent by (output started at .../sites/all/themes/THEME_NAME/template.php:1) in .../includes/common.inc on line 314.

This is most likely if you are using an editor such as Dreamweaver, in which case you should either set the encoding to utf8_unicode_ci or use a plain text editor to edit the template code.
Disabling Modules

Via the Module Administration Page in the UI

Disabling all the modules, then enabling them one by one can help narrow down a culprit module. To disable the module, simply go to the module admin page (Administer > Site Building > Modules) and uncheck the checkbox next to the module, then click "Save Configuration."

Via the Database

If your WSOD is caused by a specific module (e.g. you enabled a module, then got the white screen) and you cannot access the module admin page, it's usually effective to disable the module in the system table of the Drupal database by setting its status to 0 and then clear the cache table.
WSODs Due to Specific Modules

If you are using any of the following modules, you may want to look at these specific issues that have the potential to cause the WSOD.
Node Access

If you have just enabled or disabled node access module and get the WSOD when attempting to update, you may need to rebuild node permissions. You can do this one of two ways.

First, you can rebuild permissions via the "Rebuild Permissions" button under "Node Access Status" on the Post Settings page at http://www.example.com/admin/content/node-settings.

Alternately, you can use a script to update the db node permissions table:

<?php
require_once './includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
user_authenticate('admin', 'admin');
$actual=db_result(db_query("SELECT nid FROM {node} WHERE nid > %d ORDER BY nid",$_GET['node']));
if ($actual>0) {
  $sencer=node_load($actual);

  node_access_acquire_grants($sencer);
}
?>


<html>
<head>
<script type="text/javascript">
<!--
function delayer(){
window.location = "rebuild_permissions.php?node=<?=$actual?></pre>"
}
//-->
</script>
</head>
<body <?=($actual>0 ? " onLoad=\"setTimeout('delayer()', 500)\"" : "") ?></pre>>
<pre class="php" name="code"><?php
if ($actual>0) echo "doing... ".$actual;
else echo "Done";
?></pre>
</body>
</html>

Gallery 2 (Embedded)

If you are using embedded Gallery 2, you have to edit gallerydirectory/config.php to point to the new database server. If you do not do this, you may get the WSOD without any errors (after enabling errors as above).
WSOD on Multisite Installs

If this happens during installation or when starting a multi-site configuration where you are sharing databases or database tables, the reason may also be in the settings.php file in the sites/yoursite/ directory. This happens if you have already edited the $db_url variable in settings.php. The installer will no longer be automatically invoked, because Drupal assumes installation has already been completed.

If you get the following error messages for example (Drupal 6), the above may be the reason:

PHP Warning: Table 'testing.access' doesn't exist
query: SELECT 1 FROM access WHERE type = 'host' AND LOWER('127.0.0.1') LIKE LOWER(mask) AND status = 0 LIMIT 0, 1 in C:\\My_webdev\\htdocs\\drupal\\includes\\database.mysql.inc on line 128
PHP Warning: Table 'testing.users' doesn't exist
query: SELECT u.*, s.* FROM users u INNER JOIN sessions s ON u.uid = s.uid WHERE s.sid = 'da5qecc9rsf1dinhb4ko1e5dm0' in C:\\My_webdev\\htdocs\\drupal\\includes\\database.mysql.inc on line 128
PHP Warning: Table 'testing.cache' doesn't exist
query: SELECT data, created, headers, expire, serialized FROM cache WHERE cid = 'variables' in C:\\My_webdev\\htdocs\\drupal\\includes\\database.mysql.inc on line 128
PHP Warning: Table 'testing.variable' doesn't exist
query: SELECT * FROM variable in C:\\My_webdev\\htdocs\\drupal\\includes\\database.mysql.inc on line 128
PHP Notice: Undefined variable: variables in C:\\My_webdev\\htdocs\\drupal\\includes\\bootstrap.inc on line 427
PHP Warning: Table 'testing.cache' doesn't exist
query: UPDATE cache SET data = '', created = 1209809924, expire = 0, headers = '', serialized = 0 WHERE cid = 'variables' in C:\\My_webdev\\htdocs\\drupal\\includes\\database.mysql.inc on line 128
PHP Notice: Undefined variable: variables in C:\\My_webdev\\htdocs\\drupal\\includes\\bootstrap.inc on line 434
PHP Warning: Table 'testing.system' doesn't exist
query: SELECT name, filename, throttle FROM system WHERE type = 'module' AND status = 1 AND bootstrap = 1 ORDER BY weight ASC, filename ASC in C:\\My_webdev\\htdocs\\drupal\\includes\\database.mysql.inc on line 128
PHP Warning: Table 'testing.url_alias' doesn't exist
query: SELECT COUNT(pid) FROM url_alias in C:\\My_webdev\\htdocs\\drupal\\includes\\database.mysql.inc on line 128
WSOD on Cloned or Duplicate Sites

There are a few possible culprits for a WSOD on cloned sites.
Include Paths

If you have created a new Drupal site by copying the files from an install and then importing the db over, and then edited the settings.php file with your new db info, you may still end up with the WSOD.

The pathauto module has caused problems in this scenario, where it throws fatal errors because of missing includes. In this case, adding this line to the .htaccess file solves the problem instantaneously (the right base path for your own server may be obtained through info.php or by logging into your control panel):

php_value include_path '.:/var/www/vhosts/example.com/httpdocs'
Missing Database Tables

If you are moving a large site and using phpmyadmin to import the DB, it may have either hanged and timed out or caused some tables not be be loaded into the db. to begin, be sure to double check your db to see if all the tables are there.

If moving a large site with a huge db then better import the db via a console (command line), as phpmyadmin is not always able to handle large db files.
Relic index.html Files, and Server Setup

Sometimes a server will be setup with a blank index.html file in the root directory, and/or Apache may not be configured to search for index.php files with the DirectoryIndex directive. It can look like a WSOD, but really is just an empty page.

To check if this is the case, navigate to http://www.example.com/index.php or look for an index.html file in your root directory. If there is an index.html file, make a copy of it outside your root folder, and then delete it and see if it resolves the issue.
Infinite Loops in your Code

If your site gets a WSOD after a CPanel system update, with notices like this in the error log:

[notice] child pid ##### exit signal Illegal instruction (4)

You may have an infinite loop. Apache has become smart enough to detect this and terminate the script without waiting for a timeout.

You can try disabling modules in the system table until you find the offending module, or put a call to watchdog at the start of every function call in the module, so you can trace the call chain by looking at the watchdog table. If you find a repeating call pattern, look through the code for a loop cycling, eg. node_load through hook_nodeapi and/or other functions, and repeating node_load again.
PHP Timeout

The default PHP timeout defined in php.ini is 30 seconds. This is too short for some activities like listing/enabling modules.

Here are details on how to increase the maximum execution time.
Inappropriate naming of functions

Defining a custom function in your module code which acts as a Drupal hook function may result in WSOD. Check that the functions, that you define in your module do not use _menu, _auth or other extensions if they are not ment to implement that specific hook.

The following code generetase WSOD and Apache segfault in Drupal 5.

<?php
function mymodule_menu() {
  array(
    'path' => 'auth'
    'callback' => 'mymodule_auth'
  );
}

function mymodule_auth() {
  drupal_execute('user_login', array('name' => $_POST['name'], 'pass' => $_POST['pass']));
}
?>
Any other solutions available?

If above information was not helpful enough, you may follow the next article which is dealing with WSOD diagnostic solutions:
http://drupal.org/node/482956 - Silent WSODs (White Screen of Death) - fixing step by st

Saturday, September 26, 2009

Leet or eleet (Leet: 1337, 3l337 or l33t, 3l33t ), also known as "leetspeak"

Leet or eleet (Leet: 1337, 3l337 or l33t, 3l33t ), also known as "leetspeak", is an alphabet used primarily on the Internet for the English language. It uses various combinations of ASCII characters to replace Latinate letters. The term is derived from the word "elite", and the usage it describes is a specialized form of symbolic writing. Leet may also be considered a substitution cipher, though many dialects or linguistic varieties exist in different online communities.

The term leet is also used as an adjective to describe formidable prowess or accomplishment, especially in the fields of online gaming or in its original usage, computer hacking.

Friday, September 25, 2009

Boost your Drupal site

Boost your Drupal site!

July 23, 2007 by Justin

Posted in

Drupal




Boost: Static HTML caching for Drupal

I've recently become quite familiar with Arto Bendiken's Boost for Drupal. For context, Drupal is an open source, modular, PHP-based content management system (CMS) that I use with many of my clients. Boost is a module for Drupal which assists you in caching content as static HTML, bypassing Drupal (and thereby PHP and MySQL) in order to handle much more traffic and serve content much more quickly. Essentially, you let Apache do what it does best -- serve HTML pages. With a busy site or one with a lot of content, this can be a lifesaver.

Arto has a good write-up about Boost in his original blog post. However, Boost is a little more complex than most Drupal modules, so what I hope to add here is a couple things:

the basics of what Boost gives you

how the two "halves" of Boost complement each other

how Boost gets you outside of Drupal entirely

the status of Boost with regard to Drupal 5.x

a little more detail about how it works

some caveats that I've found

Arto's documentation for the setup of Boost is great, so I won't be rehashing that. Rather, I hope to provide a little more technical info about how the module works.

The Basics

Basically, Boost is two parts: first, a Drupal module (in the traditional sense) that manages the cache and provides for an administrative user interface, and second, some rule lines to add to your Drupal site's top-level .htaccess file which allow Apache to bypass Drupal entirely and serve pages from the cache.

The biggest thing to understand about how Boost works is to understand its utilization of Apache's mod_rewrite via the .htaccess file rule lines (by the way, .htaccess is just the default name for files in your site that Apache will read for configuration info). Many people may not understand that Drupal's use of clean URLs is dependent upon mod_rewrite. Every URL on a Drupal site is basically just a path argument to the top-level index.php, which dispatches calls to various points in the code to handle that argument. So, when you go to /about, the index.php file actually gets an argument of about and determines what content to serve. Apache's mod_rewrite is able to keep the browser pointed at /about while actually running index.php. Another popular open source CMS, WordPress, behaves similarly.

Once you understand this, it's easy to understand what Boost does and why it requires mod_rewrite. Boost by default stores the cached versions of pages under /cache on your website (this path is configurable, though). Then, when a request comes in, the .htaccess file is consulted (because that's what Apache does), which tells it to look for cache files first before sending anything to Drupal's index.php. Since the cache files are plain HTML, they go out much more quickly than Apache running PHP, firing up Drupal, querying MySQL, and then serving content. Arto provides some graphs in his original post showing just how dramatic this improvement can be.

Lastly, a word about the cache filename standard. If in fact the /about URL were cached, it would actually be in your site at /cache/about.html. If Apache finds this file, it assumes that the cache is still valid (the Drupal module side takes care of expiring and removing stale content) and serves it directly. For path aliases (such as "/about should serve the same content as /node/137"), Boost uses UNIX symbolic links in the cache filesystem, so /cache/about.html would be a link to /cache/node/137.html.

Boost and Drupal 5.x

I have been using Boost on a Drupal 5.1 site, thanks to this port of Boost to Drupal 5.x by the maintainer of drupal.ru. This seems to be the only source of Drupal 5.x-compatibile Boost material currently. The only caveat to be aware of about this version is that by default, the front page is not cached -- more on this below. If your site is anything like the one I used Boost on, you will need to remedy this since your front page is likely your busiest as well as most complicated page and is in need of caching.

A Little More Detail

A couple other notes about Boost's operation:

Cache files are created on demand. For example, if your front page is not cached when someone requests it, Drupal will construct the page and cache the file, but serve the constructed page to the user. Every user thereafter, until the cache file becomes stale and is removed, will receive the cached version. If you have pages that are particularly demanding, think about running a cron to request them anonymously in order to get them cached for regular users.

Special paths like /user/login and /admin, as well as HTTP POST requests and any request for a logged-in user, are not cached. Arto has put a lot of thought into this area. Note that this means that sites with mostly logged-in users will not benefit from Boost very much -- anonymous users see the real benefit.

Boost takes over the configuration interface for Drupal's built-in caching mechanism. This just means that it avoids confusion between two types of caching and just "upgrades" your current setup to be Boost-ified.

Like the built-in cache, Boost has multiple cache lifetime intervals to choose from; anywhere from one minute up to one day.

Boost expires content in one of two ways. It implements hook_nodeapi to catch node updates, insertions, and deletions and responds to those, and it also implements hook_cron to expire content that has become stale but has not had any specific actions performed on it.

Technical note: Boost uses PHP's output control functions (i.e. ob_start et al.) and hook_init to intercept every Drupal page request, buffer the content, compare to and update the cache, and then send the content along through Drupal normally.

Nothing stops you from expiring content manually by deleting its file from the cache. However, note that for pages which have path aliases (and thus Boost symbolic links) to them, the links do not get removed automatically so you may cause some wonkiness by doing this.

Boost inserts a small HTML comment at the very bottom of cached pages with the start and end cache times so that you can tell if it's working and how long a given file will persist in the cache.

Caveats

Like any somewhat intrusive technology (and by this I mean that it works with every page and changes the way your site operates as a whole), Boost should be used with caution. Arto states that the project is still in an alpha state.

The biggest issue that I've noticed is a strange bug which occasionally caches the front page as a Drupal "access denied" page. Others have seen this as well and I've never been able to nail it down completely. This is the main reason why drupal.ru's port of Boost to Drupal 5.x leaves out the front page from caching. I was able to work around this by hacking Boost's boost.api.inc file, in the boost_cache_set function, to not cache pages containing the words "access denied". I hope to report more once I figure this out.

The second issue is that currently, Boost will not work for sites that are not at the top-level. That is, if your site is domain.com/mysite, it will not work -- only domain.com would work. I believe this is on the .htaccess side, but it only really affected me in testing a development version of the site and since I was able to set up a top-level sandbox, I didn't investigate it any further. Once again, if I make any improvements in this area, I'll update this post.

Conclusion

This concludes my overview of Boost. As I mentioned above, I will update this post if I make any progress on the (very minor) issues that I've had with it. It's a great system and I highly recommend it!

You may also be interested in my Drupal page here at Code Sorcery Workshop for more info about my work with Drupal.

Thanks for reading!

-->

Trackback URL for this post:

http://codesorcery.net/trackback/73

Login or register to post comments





RSS feed for comments to this post


[/url]

Submitted by [url=http://bendiken.net/]Arto Bendiken
(not verified) on July 23, 2007.

Great article, Justin - and a cool logo to boot :-) Glad to hear you persevered to get the module working for 5.x. It does indeed still have a lot of rough edges, but once you grok its modus operandi (and you clearly have) it's based on pretty straightforward concepts underneath.

I've added a link to this article to my original blog post about Boost and to the Drupal.org project page, too. If you have the chance, would you please share your changes for 5.x in the issue tracker - this article certainly gives me a kick in the behind to get a release 1.0 of the module out for 5.x soonest. (The project co-maintainer position is also up for grabs if you want it.)

Login or register to post comments

[/url]

Submitted by Justin on July 23, 2007.

@Arto: I'd be happy to help maintain Boost, as well as some of the other modules that we've talked about. I'll email you about these before too long.

[url=http://codesorcery.net/user/login?destination=comment%2Freply%2F73%23comment-form]Login
or register to post comments

[/url]

Submitted by [url=http://www.improvetheweb.com/]Yuri
(not verified) on July 24, 2007.

Justin, can you make the working Drupal 5.x module (with your homepage tweak) available? That'd be great. Thanks.

By the way, if you could use some SEO/usability/copywriting consulting, I am willing to barter to speed up the development of the module.

Login or register to post comments

[/url]

Submitted by [url=http://www.mcdevzone.com/]Mike
(not verified) on July 24, 2007.

What about sites that display a different theme & layout for mobile devices? I had to turn caching off because it prevented anonymous users on a iPhone from seeing the optimized view since the standard home page was cached.

Login or register to post comments

[/url]

Submitted by [url=http://themegarden.org/]themegarden.org
(not verified) on August 4, 2007.

Whati is the status of the Drupal 5.x port of the boost module?

Why port from drupal.ru isn't commited to drupal.org (boost module)?

Login or register to post comments

[/url]

Submitted by [url=http://www.blinkitshere.com/]Cozmo
(not verified) on August 6, 2007.

This is an amazing module. Very, very, very needed.

My biggest question is - without it working on subdomains I personally would be very nervous to light it up on any production site. I always test in a staging area first (stage.LiveDomain.com.) then push live.

What is the recommended course of action?

Login or register to post comments

[/url]

Submitted by [url=http://bendiken.net/]Arto Bendiken
(not verified) on August 6, 2007.

@Cozmo: Boost has no problem with subdomains (after all, what else is www.livedomain.com but a subdomain of livedomain.com?). The limitation is that the Drupal site needs to be in the root directory.

In other news, the 5.x version is now available from drupal.org. And guys, please note that the right place (I should say, the one and only place) to request support for this module is at http://drupal.org/project/boost. Support requests in comments on blogs here and there will most likely simply be ignored by everyone involved.

Login or register to post comments

[/url]

Submitted by brush (not verified) on September 3, 2007.

for the subdirectory issue, see contemporaneous comment at [url=http://drupal.org/node/101147]http://drupal.org/node/101147
for a hack that is working for me.

.b

Login or register to post comments

Nice write up. I like boost

Submitted by ekes (not verified) on July 24, 2008.

Nice write up. I like boost too. You can also have smaller threads in your webserver if it's just serving static content, it can be pretty light in fact. So for a step beyond I tried running the apache sever with the static content somewhere else:-

http://iskra-it.com/blog/ekes/2008/01/10/boost-module-and-rsynced-apache...

Login or register to post comments

Any rules for boost to work

Submitted by Fellow Gnome (not verified) on August 24, 2008.

Any rules for boost to work with lighttpd ? (in .lua or lighttpd.conf)

Login or register to post comments

Re: lighttpd: No, I don't

Submitted by Justin on August 24, 2008.

Re: lighttpd: No, I don't have any experience with it. I'll contact Arto and see if he has heard of anything, but I've only used it (and Drupal, for that matter) with Apache.

Login or register to post comments

I have posted some lua code

Submitted by drupdrips (not verified) on September 6, 2008.

I have posted some lua code (if you are using mod_magnet) for boost module to work with lighttpd.

Check the link:

http://drupal.org/node/150909#comment-997460

Try it out and pls provide any feedback to make it better.

Thks.

Login or register to post comments

@drupdrips: Thanks for this.

Submitted by Justin on September 8, 2008.

@drupdrips: Thanks for this. I'll leave the comment for folks to find in this post and if I ever get to trying lighttpd myself in this way, I'll give them a shot.

Login or register to post comments

edit master.passwd

Etc/master.passwd
From FreeBSDwiki
Jump to: navigation, search

Etc/master.passwd is the master password file on FreeBSD. Its Linux equivalent is the shadow password file. Don't ever edit master.passwd directly. Use vipw, which calls the default editor (likely ee or vi) but checks for any format mistakes before allowing a save. As with Linux, /etc/passwd will have any public information such as shell info and so on.

Sunday, September 20, 2009

programmatically node edit content type

programmatically node edit content type
<?php
$n = node_load(array('nid' => $nid));

// Edit node fields here, e.g. change $n->title

node_save($n);
?>

Saturday, September 19, 2009

Theme a CCK node content type input form in Drupal 6

<?php
function myModule_cck_node_type_name_theme() {
  return array(
    'cck_node_type_name_node_form' => array(
      'arguments' => array('form' => NULL),
      //'file' => 'user.admin.inc',
      //'template' => 'ccktype', // you can also use template to theme the node form.
    ),
  );
}

function theme_cck_node_type_name_node_form($form) {
  $output = '';
  $buttons = '';

  foreach (element_children($form) as $key) {
    if ($key == 'buttons') {
      $buttons = drupal_render($form[$key]);
    }
    else {
      $output .= '' . drupal_render($form[$key]) . '';
    }
  }

  $output .= $buttons . '';

  $output .= 'add more stuff here';

  return $output;
}

drush is a command line shell and Unix scripting interface for Drupal

drush is a command line shell and Unix scripting interface for Drupal, a veritable Swiss Army knife designed to make life easier for those of us who spend some of our working hours hacking away at the command prompt.

Using AHAH to dynamically generate form elements (and integration with multi-tiered taxonomy)

Using AHAH to dynamically generate form elements (and integration with multi-tiered taxonomy)
Submitted by Eric on Sun, 09/13/2009 - 23:41

For some time now I've wanted to write a blog entry about using AHAH to create dynamically generated form elements. After a recent conversation at work regarding usability, I now had a real world example to create: how to use tiered taxonomy to dynamically generate a form. This code snippet will show you how to create a form that creates child select dropdowns based on the parent taxonomy term the user selects.

First I established a multi-tier taxonomy called "AHAH":

For this example I created a menu callback to display my initial form:
<?php
function helper_menu() {
 
  $items = array();
 
  $items['ahah-form'] = array(
    'title' => 'AHAH Form',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('_helper_callback_ahah_form'),
    'type' => MENU_CALLBACK,
    'access callback' => 'user_access',
    'access arguments' => array('access content'),
  );
 
  return $items;
 
}

I then defined the page callback to show the initial form:
<?php
function _helper_callback_ahah_form() {

  // define an array to contain form elements
  $form = array();
 
  // define the top level vid
  $vid = 2;
 
  // fetch a tree of taxonomy elements
  $tree = taxonomy_get_tree($vid, 0, -1, 1);
 
  // loop though taxonomy and collect elements
  $options = array();
  foreach ($tree as $key => $value) {
    $options[$value->tid] = $value->name;
  }
 
  // create the first select dropdown input
  $form['select_1'] = array(
    '#type' => 'select',
    '#options' => $options,
    '#title' => t('Select 1'),
    '#size' => 5,
    '#multiple' => false,
    '#ahah' => array(
      'event' => 'change',
      'path' => 'ahah-form-callback',
      'wrapper' => 'wrapper-1',
      'method' => 'replace',
    ),
  );
 
  // pass the top level vid in the form
  $form['ahah_vid'] = array(
    '#type' => 'hidden',
    '#value' => $vid,
  );
 
  // create an empty form element to contain the second taxonomy dropdown
  $form['wrapper_1'] = array(
    '#prefix' => '
', '#suffix' => '
', '#value' => ' ', ); // add a form submit button $form['submit'] = array( '#value' => 'Submit', '#type' => 'submit' ); return $form; }

The above form callback produces the following:

Next, I defined a callback to handle the AHAH page request:
<?php
// new menu item:
function helper_menu() {
 
  // ...
 
  $items['ahah-form-callback'] = array(
    'title' => 'AHAH Form Callback',
    'page callback' => '_helper_callback_ahah_form_callback',
    'type' => MENU_CALLBACK,
    'access callback' => 'user_access',
    'access arguments' => array('access content'),
  );
 
  // ...

  return $items;
 
}

// and, here's the AHAH callback used to create the new form elements:
function _helper_callback_ahah_form_callback() {
 
  // define a string variable to contain callback output
  $output = "";
 
  // pull the top level vid from the $_POST data
  $vid = $_POST['ahah_vid'];
 
  // pull the selected dropdown from the $_PODT data
  $parentVid = $_POST['select_1'];
 
  // loop through the taxonomy tree and fetch child taxonomies
  $options = array();
  $tree = taxonomy_get_tree($vid, $parentVid, -1, 1);  
  foreach ($tree as $key => $value) {
    $options[$value->tid] = $value->name;
  }
 
  // define the second tier select dropdown element
  $form['select_2'] = array(
    '#type' => 'select',
    '#options' => $options,
    '#title' => t('Select 2'),
    '#size' => 5,
    '#multiple' => false,
  );
 
  // rebuild form object and output new form elements
  $output .= ahah_render($form, 'select_2');
 
  // render form output as JSON
  print drupal_to_js(array('data' => $output, 'status' => true));
 
  // exit to avoid rendering the theme layer
  exit();
 
}

// Lastly, here's a help function pulled from Nick Lewis's blog to alter the form
// see: http://www.nicklewis.org/node/967
// NOTE: based on poll module, see: poll_choice_js() function in poll.module
function ahah_render($fields, $name) {
  $form_state = array('submitted' => FALSE);
  $form_build_id = $_POST['form_build_id'];
  // Add the new element to the stored form. Without adding the element to the
  // form, Drupal is not aware of this new elements existence and will not
  // process it. We retreive the cached form, add the element, and resave.
  $form = form_get_cache($form_build_id, $form_state);
  $form[$name] = $fields;
  form_set_cache($form_build_id, $form, $form_state);
  $form += array(
    '#post' => $_POST,
    '#programmed' => FALSE,
  );
  // Rebuild the form.
  $form = form_builder($_POST['form_id'], $form, $form_state);

  // Render the new output.
  $new_form = $form[$name];
  return drupal_render($new_form);
}

The above code allows the user to select an option from the top level tier of taxonomy and the AHAH callback will generate the a select dropdown of the child taxonomies as shown below:

On form submission, you'll see that the options the user selected as stored in $form_state['values']['select_1'] and $form_state['values']['select_2']

Drupal node_save no longer returns $nid, so how do I get it?

Drupal node_save no longer returns $nid, so how do I get it?



I have code like this:

... setup $issue object ...
$node = node_save($issue);

print_r($node);

The node is created successfully, and everything works fine...but nothing it returned from save_node(). Older docs indicate that it returns $nid. Several discussions and tickets indicate that in recent Drupal versions the node object is returned, but I get nothing back (and $node->nid is empty).

So, how do I find out the nid of the newly created node?

OK, finally figured this one out (and boy do I feel silly).

node_save now operates on the existing node object (already defined in $issue in my case), and simply adds the nid field (among others) to the existing object. Nothing is returned, but I can access the nid with $issue->nid after node_save has run.

Programmatically create a CCK node

Programmatically create a CCK node
Submitted by Eric on Mon, 04/14/2008 - 11:20

Here's my code snippet, I use it all the time. NOTE: replace {BLAH} with your data.

<?php
// add node properties
$newNode = new stdClass();
//$newNode = (object) NULL;
$newNode->type = '{NODE_TYPE}';
$newNode->language = '';
$newNode->title = '{NODE_TITLE}';
$newNode->uid = {USER_ID};
$newNode->created = strtotime("now");
$newNode->changed = strtotime("now");
$newNode->status = 1;
$newNode->comment = 0;
$newNode->promote = 0;
$newNode->moderate = 0;
$newNode->sticky = 0;

// add CCK field data
$newNode->field_{YOUR_CUSTOM_FIELD_1}[0]['value'] = '{DATA_1}';
$newNode->field_{YOUR_CUSTOM_FIELD_2}[0]['value'] = '{DATA_2}';

// save node
node_save($newNode);

NOTE: the structure of the CCK fields can vary. Here's one way to see the structure of an already created CCK node...

<?php
$sampleNode = node_load('{ALREADY_CREATED_CCK_NODE_ID}');
echo "
" . print_r($sampleNode, TRUE) . "
";

Friday, September 18, 2009

Set Content Type Node Title to a default value

Method 1: Use Automatic Nodetitles module

Method 2: hook_form_alter()

<?php
/**
* Implementation of hook_form_alter().
*/
function YourModuleName_form_alter(&$form, $form_state, $form_id) {
  if (isset($form['type']) && isset($form['#node'])) {
    if ($form_id === 'your_content_type_name_node_form') {
      $form['title']['#default_value'] = 'asdf';
      //$form['title']['#value'] = 'asdf';
      $form['title']['#required'] = FALSE;
    }
  }

}
?>
Try to Clear Cache if things don't work.

Make a CCK field become hidden while creating a content type (node)

Make a CCK field become hidden while creating a content type (node)

Method 1: computed field

Method 2: use the content permissions module and don't give the view field permission to whatever roles you don't want to see it.

Method 3: CCK-specific hooks

Method 4:
<?php
/**
* Implementation of hook_form_alter().
*/
hook_form_alter() {
  $form['field_CCK_Field_Name']['#prefix'] = "<div style='display: none;'>
";
  $form['field_CCK_Field_Name']['#suffix'] = "</div>
";
}
?>

Method 5:
Following code is able to make a CCK field becoming a hidden type field.

<?php
/**
* Implementation of hook_form_alter().
*/
function YourModuleName_form_alter(&$form, $form_state, $form_id) {
  if (isset($form['type']) && isset($form['#node'])) {
    ### Make a CCK field becoming a hidden type field.
    // ### Use this check to match node edit form for a particular content type.
    if ($form_id === 'YourContentTypeName_node_form') {
      $form['field_NameToBeHidden'][0]['#default_value']['value'] = 2012;
      $form['#after_build'] = array('_test_set_cck_field_to_hidden');
    }
  }
}

/**
*
* @param
* @return
*/
function _test_set_cck_field_to_hidden($form, &$form_state) {
  $form['field_NameToBeHidden'][0]['value']['#type'] = 'hidden';
  $form['field_NameToBeHidden'][0]['#value']['value'] = 'testValue';

  return $form;
}
?>

Something Related:
You should be able to alter #default_value attribute for any element in the form directly from hook_form_alter().

<?php
function mymodule_form_alter(&$form, $form_state, $form_id) {
  // Is this the node edit form?
  if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) {
    if ( /* whatever else condition to find the form you're interested in */ ) {
      // The field is at the top level of the form?
      if (isset($form['field_myfield'])) {
        // Assign here whatever suits your needs.
        $form['field_myfield']['#default_value'] = 'myvalue';
      }
      // The field is inside a fieldgroup?
      if (isset($form['group_mygroup']['field_myfield'])) {
        // Assign here whatever suits your needs.
        $form['group_mygroup']['field_myfield']['#default_value'] = 'myvalue';
      }
    }
  }
}
?>

However, you should be sure that your module is loaded after the one that generates the form you intend to alter. You can do this by altering the weight of the module in the {system} table from the hook_install() of your module. See the fieldgroup module in CCK to see an example of this.

<?php
/**
* Something like this should be placed in your mymodule.install
*/
function mymodule_install() {
  db_query("UPDATE {system} SET weight = 10 WHERE name = 'mymodule'");
}
?>

Reference: http://drupal.org/node/357328

Thursday, September 17, 2009

Code snippet: How to set the disabled attribute of a CCK field

Code snippet: How to set the disabled attribute of a CCK field
Drupal 6.x · No known problems
Last modified: August 4, 2009 - 01:00

Here's a code snippet that you can use to set the disabled attribute of a CCK field.

First, you need to create a small module containing the following code:

<?php
/**
* @file
* Custom module to set the disabled attribute of CCK fields.
*/

/**
* Implementation of hook_form_alter().
*/
function mysnippet_form_alter(&$form, $form_state, $form_id) {
  if (isset($form['type']) && isset($form['#node'])) {
    // Use this check to match node edit form for a particular content type.
    if ('mytype_node_form' == $form_id) {
      $form['#after_build'][] = '_mysnippet_after_build';
    }
    // Use this check to match node edit form for any content type.
//    if ($form['type']['#value'] .'_node_form' == $form_id) {
//      $form['#after_build'][] = '_mysnippet_after_build';
//    }
  }
}

/**
* Custom after_build callback handler.
*/
function _mysnippet_after_build($form, &$form_state) {
  // Use this one if the field is placed on top of the form.
  _mysnippet_fix_disabled($form['field_myfield']);
  // Use this one if the field is placed inside a fieldgroup.
//  _mysnippet_fix_disabled($form['group_mygroup']['field_myfield']);
  return $form;
}

/**
* Recursively set the disabled attribute of a CCK field
* and all its dependent FAPI elements.
*/
function _mysnippet_fix_disabled(&$elements) {
  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {

      // Recurse through all children elements.
      _mysnippet_fix_disabled($elements[$key]);
    }
  }

  if (!isset($elements['#attributes'])) {
    $elements['#attributes'] = array();
  }
  $elements['#attributes']['disabled'] = 'disabled';
}
?>

Explanation:

Setting the #disabled attribute of the element in form_alter doesn't work because the FAPI process handler of the CCK field won't transfer the #disabled attribute to its children elements. So we need to find a different method...

We can do this using our own #after_build handler, but here we cannot simple set the $element['#disabled'] attribute of the element. The reason is this attribute is transformed into $element['#attributes']['disabled'] by _form_builder_handle_input_element executes(), which is the format FAPI theme functions know about. However, _form_builder_handle_input_element executes() is executed before #after_build handlers are invoked, so our own handler needs to set the value as the theme functions expect. See form_builder().

Finally, we need to use a recursive function so that we can set the disabled attribute of all the FAPI elements that depend on the CCK field we're interested in. Think for example about a checkboxes element, etc.

Caveats:

Disabled attributes are not sent to the server with the form. The $_POST array will not have any value for disabled elements, and this may cause unexpected results. Also, if the element is required, will see the error 'field field is required.'.

Possible workaround:

Ok, now we're going to make a copy of the field that we want to disable, we will disable that one, and set the type of the original field to 'hidden'. I'm not sure if this technique will work for any kind of field, but here's a simple example that works:

Let's disable the node title. Well, to say something useful, let's imagine that our custom code also wants to assign a title to the node programmatically. In this case, we'll use the same code as above, but replacing the after_build function with this one:

<?php
/**
* Custom after_build callback handler.
*/
function _mysnippet_after_build($form, &$form_state) {
  // Let's check the title exist.
  if (isset($form['title'])) {

    // Replace the element with an array that contains 2 copies.
    $form['title'] = array(
        'title' => $form['title'],
        '_disabled_title' => $form['title'],
    );

    // Now, we change the type of the original field to 'hidden'.
    $form['title']['title']['#type'] = 'hidden';

    // Now, we can use our recursive function to set the disabled attribute
    // or the field and all its own child elements. The title doesn't have children
    // but maybe another field that you are interested in does.
    _mysnippet_fix_disabled($form['title']['_disabled_title']);
  }
  return $form;
}
?>

Now, we have a form where the 'visible' title element is disabled. The original field is hidden, which is the one that will be sent to the server with the form, and so we won't see the 'field is required' error. :)

Another possible workaround:

Here we're going to copy the '#default_value' attribute of the element to the '#value' attribute. Depending on the type of field, this method may also work, and it is actually a bit more simple.

<?php
/**
* Custom after_build callback handler.
*/
function _mysnippet_after_build($form, &$form_state) {
  // Let's check the title exist.
  if (isset($form['title'])) {

    // Explicitly set the #value attribute of the element.
    $form['title']['#value'] = $form['title']['#default_value'];

    // Now, disable the element and its own children.
    _mysnippet_fix_disabled($form['title']);
  }
  return $form;
}
?>
‹ Code snippet: How to force uppercase in CCK fields up Code snippet: node reference multiple select without CTRL ›
» Login or register to post comments
Thanks!
ximo - January 20, 2009 - 14:35

Thanks a million for this! I couldn't believe CCK won't allow one to disable fields, but this technique did the trick.
--
Joakim Stai
NodeOne
Login or register to post comments
Works great thanks
pcambra - February 5, 2009 - 09:44

Works great thanks Markus

Does anybody know how to keep the default elements selected (i.e. a multiselect) with the element disabled?

Thanks
Login or register to post comments
or how to change any other
dazmcg - March 19, 2009 - 21:00

or how to change any other attributes of the element? Like #default_value?

thanks!
Login or register to post comments
From form_alter itself?
markus_petrux - March 19, 2009 - 22:43

You should be able to alter #default_value attribute for any element in the form directly from hook_form_alter().

<?php
function mymodule_form_alter(&$form, $form_state, $form_id) {
  // Is this the node edit form?
  if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) {
    if ( /* whatever else condition to find the form you're interested in */ ) {
      // The field is at the top level of the form?
      if (isset($form['field_myfield'])) {
        // Assign here whatever suits your needs.
        $form['field_myfield']['#default_value'] = 'myvalue';
      }
      // The field is inside a fieldgroup?
      if (isset($form['group_mygroup']['field_myfield'])) {
        // Assign here whatever suits your needs.
        $form['group_mygroup']['field_myfield']['#default_value'] = 'myvalue';
      }
    }
  }
}
?>

However, you should be sure that your module is loaded after the one that generates the form you intend to alter. You can do this by altering the weight of the module in the {system} table from the hook_install() of your module. See the fieldgroup module in CCK to see an example of this.

<?php
/**
* Something like this should be placed in your mymodule.install
*/
function mymodule_install() {
  db_query("UPDATE {system} SET weight = 10 WHERE name = 'mymodule'");
}
?>
Login or register to post comments
the important bit works
dazmcg - March 20, 2009 - 15:42

the important bit works :)

Setting the weight manually didn't seem to work but I haven't spent much time on that as I used the util module to set the weight and will solve that minor puzzle later.

Once the weight is set, it does indeed build the cck fields into the form, enabling me to do the things i wanted to do. thanks again markus!

Is there a better way to do this which doesn't rely on module weights? I guess one thing I could do is in the hook_install (when i've gotten it to work) is to check CCK's weight and just +1 it?

THANKS!
Login or register to post comments
Thanks for this snippet--very
ppmax - April 6, 2009 - 13:47

Thanks for this snippet--very useful.

I have a form with a cck field dependency:
field1: required
field2: required only if a value selected in field1 evaluates to TRUE

I set up both cck fields as required, but then based upon user input for field1 I hide and disable field2 using jquery.

Unfortunately, setting the attribute of field2 to "disabled" doesnt work, and the form fails validation because field2 is "still" required.

My question:
How would one use this technique in the context of javascript? How do I dynamically (client-side) set '#required' to FALSE?

thx
pp
Login or register to post comments
'#required' is FormAPI attribute so you can't client-side
markus_petrux - April 6, 2009 - 13:58

'#required' is FormAPI attribute so that's what you need to alter, server side. And this would require a different technique, I'm afraid. I would post in the forums so that the book page doesn't mix too much different stuff which end up hard to find later.
Login or register to post comments
Thanks for the reply-- After
ppmax - April 6, 2009 - 19:16

Thanks for the reply--

After posting I had a hand-slap to forehead moment when I realized my solution wouldnt work for server-side form props--duh. I'll take this to the forums--sorry for posting to the handbook page ;)

pp
Login or register to post comments
I have a slightly different
gausarts - April 10, 2009 - 23:35

I have a slightly different need. I want to remove the title field without autonodetitle. Can we do the unset then?

I tried placing unset($form['title']['_disabled_title']['#title']); and unset($form['title']['title']['#title']); after $form['#after_build'][] = '_mymodule_after_build'; on mymodule_form_alter together with the above code, but each didn't do anything. The title is just disabled, but not disappear. Any hint?

Thanks

Update:
Placing either unset($form['title']); or $form['title'] = array(); on mymodule_form_alter did the trick. But I wonder if it's the right thing to do?

love, light n laughter
gausarts
Login or register to post comments
Trying to set #default_value
doublejosh - May 28, 2009 - 19:47

Not getting much success setting the default value...

<?php
function _my_module_after_build($form, &$form_state) {
  _my_module_default_clear($form['field_url'][0]);
  return $form;
}

function _my_module_default_clear(&$elements) {
  $elements['#default_value']= 'http://';
}

function my_module_form_alter(&$form, $form_state, $form_id) {
  if($form_id=='post_node_form') {
    $form['field_url'][0]['#after_build'][] = '_my_module_after_build';
  }
}
?>

(doing so because cck links don't allow a default of 'http://' as it's not a valid link)
Login or register to post comments
Try to set #attributes = array('onClick' => 'javascript:fun(););
swatee_karpe - July 25, 2009 - 13:59

Please help me to set #attributes = array('onClick' => 'javascript:fun();');

I use drupal 5x and create cck field say payment with two radio buttons by cc and by check.
so i want to give a javascript function call on click of by cc button.

so i m not getting how to set this already created button using UI.

Swatee Amit Karpe

FreeBSD VMware Interfaces

FreeBSD VMware Interfaces

A site hosting news on FreeBSD 7.0 also included several great tips for FreeBSD under VMware. One tip talked about the lnc network interface standard under VMware.

You can see lnc0 in this sample VM. Here's dmesg output:

lnc0:

port 0x1400-0x147f

irq 18 at device 17.0 on pci0

lnc0: Attaching PCNet/PCI Ethernet adapter

lnc0: [GIANT-LOCKED]

lnc0: Ethernet address: 00:0c:29:38:7d:ea

lnc0: if_start running deferred for Giant

lnc0: PCnet-PCI

This is what the interface looks like in VMware:

taosecurity:/root# ifconfig lnc0

lnc0: flags=108843 mtu 1500

        inet6 fe80::20c:29ff:fe38:7dea%lnc0 prefixlen 64 scopeid 0x1

        inet 198.18.153.167 netmask 0xffff0000 broadcast 198.18.255.255

        ether 00:0c:29:38:7d:ea

The fact that lnc is GIANT-locked is bad for network performance. Furthermore, lnc is deprecated in FreeBSD 7.0, replaced by le.

The site included a tip to replace the lnc0 driver with an emulated em0 driver. I modified my .vmx file by adding the second line.

Ethernet0.present = "TRUE"

ethernet0.virtualDev="e1000"

With that option, I see em0 in dmesg output:

em0:

port 0x1070-0x1077

mem 0xec820000-0xec83ffff,0xec800000-0xec80ffff irq 18

at device 17.0 on pci0

em0: Memory Access and/or Bus Master bits were not set!

em0: Ethernet address: 00:0c:29:fd:f6:1d

Here is the device in ifconfig output:

kbld:/root# ifconfig em0

em0: flags=8843 mtu 1500

        options=b

        inet6 fe80::20c:29ff:fefd:f61d%em0 prefixlen 64 scopeid 0x1

        inet 198.18.152.169 netmask 0xffff0000 broadcast 198.18.255.255

        ether 00:0c:29:fd:f6:1d

        media: Ethernet autoselect (1000baseTX )

        status: active

That's really cool, especially since em0 doesn't need the GIANT lock. Performance should be improved. The site I mentioned said this with respect to replacing lnc with le:

le is still a SIMPLEX device but at least it's not GIANT-locked.

It's true that le is not GIANT locked, but I think SIMPLEX is really irrelevant. You'll see SIMPLEX everywhere, like in the above em0 output. As I mentioned here, SIMPLEX "has nothing to do with half or full duplex. SIMPLEX refers to the NIC not being able to transmit and receive while operating on true CSMA/CD Ethernet, which is really not the case with switched networks."

On a related note, you don't have to recompile your kernel to use le. You can load it as a kernel module during boot by entering if_le_load="YES" in /boot/loader.conf as noted in the le man page. In my tests this did not result in using the le driver instead of lnc however, so those wishing to use an lnc0 replacement should use em0.

I also concur with the recommendation to use kern.hz="100" in /boot/loader.conf to deal with VMware timing issues. That helps immensely and doesn't require VMware Tools installation.

VMware Workstation 6.5 now defaults to the e1000 (em0) ethernet adapter when either "FreeBSD" or "FreeBSD 64-bit" is selected as the Guest operating system during the New Virtual Machine Wizard.

速度

速度

在現今競爭激烈的世界,速度已經成為成敗與否的重要關鍵要素,鴻海集團(富士康)董事長郭台銘先生,曾經有一句名言,「在今天的世界,沒有『大』的打敗『小』的,只有『快』的打敗『慢』的」,這句話用在現在的任何職場競爭,都是非常適用的,因為很多機會都在「等待」、「猶豫」中失去了成功的契機。

最近我跟一位事事講求效率的主管開會,在他主持的會議裡,只能用「快、狠、準」三個字來形容,在會議裡只要部屬給他的答案是不確定,他就會引述鴻海集團(富士康)董事長郭台銘先生的名言,「在今天的世界,沒有『大』的打敗『小』的,只有『快』的打敗『慢』的」,然後語重心長的跟所有部屬們問說「我們想要等,但是我們的顧客會願意等待嗎?」,對他而言所謂的「執行力」,就是「速度」、「準度」、「精度」的全面貫徹,而速度則是執行力的精神所在。

記得有個「兩位旅人被熊追」的寓言故事,「有兩個人在路上碰到一隻大熊,一個人拔腿就跑,另一人則蹲下來繫鞋帶,前者問:『你繫鞋帶幹嘛?你能跑的比熊快嗎?』後者答:『我不需要跑的比熊快,我只要跑贏你就行』了」,這個故事告訴了我們,我們無法去改變所處的週遭外在環境,但是,我們只要能跑的比競爭對手快一步,就會有成功的機會了,所以事情只要自己已經確立了目標、下定決心之後,就全力以赴去執行,相信就算再強的對手,或者是碰到再困難的事情,都可以迎刃而解的。

「三國志.魏書.郭嘉傳」有句名言「兵貴神速」,這則成語的意思是用兵以行動迅速爲可貴,也就是用兵神速,出其不意,攻其無備,就會取得勝利,而「孫子兵法」十三篇的第一篇,計篇中也提到「攻其無備,出其不意;此兵家之勝,不可先傳也」,意思是說作戰要去攻擊敵人,防備上出現的空隙和漏洞,要採取出乎敵人意料之外的突然行動,這是將領取勝的秘訣,是不能事先表露出心機的,而無獨有偶的號稱西方戰略兵聖的軍事專家-克勞塞維茲(Kral Von Clausewitz),在其名著「戰爭論」的第三篇,「戰略概論」中的第九章「出敵不意」,也提到以快速與出乎敵人意料之外的突然行動,來達到軍事作戰的勝利,所以不管東西方的戰略、兵法大師,也都不約同的提出了,「速度」是影響整個戰局勝負的重要關鍵要素,當然也適用於激烈的企業競爭規則了!

人生其實有很多的機會,都是在「等待」、「猶豫」中失去了契機,因為就在這個「停下來」的時候,其他的競爭者馬上就搶先一步,奪走了這個機會,就像我們主管所說的「我們願意等待,但是別人會願意等待嗎?」,在這講求速度的世代裡,速度慢的人永遠都很難擁有好的機會,我想這也許可以提供給很多,剛入職場的年輕人來省思的。

下班後你在做什麼?

下班後你在做什麼?

要看一個人未來是否有競爭力,就看他下班之後做了些什麼?有人下班了,就坐在電視機前或電腦前什麼都不做,但是有些人很會充分安排與利用時間,既讓自己充分休息、也讓自己思考、也會讓自己學習,長久下來,會充分利用時間的人,不僅在職場上取得領先,他的人生也會比其他人過的還燦爛。

最近有同事跟我聊到,自己下班之後總是覺得好無聊,我笑著問他「為什麼覺得很無聊呢?」,這位同事回答我說「每天下班了他就坐在電視機前看電視,日復一日」,我聽了就問他「那除了看電視之外你下班了還做什麼事情?」,他聯想都沒想就回答我說「上網看電影跟去外面晃晃」,我聽完之後問他「你從來都沒有去想過與規劃自己下班後的時間嗎?」,這位同事搖搖頭說「沒有啊!幹麻這麼累,想到什麼就做什麼了」,無獨有偶的是我有另位同事,卻跟他是完全不一樣,每天都會讓自己在11點前上床休息,而從下了班到上床睡覺這段時間,他會去看一些書,思考工作或生活上的一些事情,然後寫寫自己喜歡的文章,每週也有幾天晚上會去運動,假日除了到郊外去走走之外,也會去逛逛圖書館看看書。

上帝給予每個人每天的時間都是一樣的,只是有人會將自己的時間揮發到淋漓盡致,但是而有些人,卻是將自己的時間過的黯淡無光,等到過了一段時間之後,才猛然的發覺到,為什麼別人的生活可以那麼的多彩多姿,但是,自己好像卻是這麼虛度光陰,什麼也學不到、什麼也沒得到,隨著自己的年華逐漸老去,就開始不斷的去怨天尤人,可是卻從來不去思考,自己長期以來浪費自己「時間」的方式,這或許也是曾經有人這麼說「可憐之人必有可惡之處」,真正的真諦之所在。

記得小時候讀過一句成語叫做「鐵杵也能磨成繡花針」,這句成語真正的涵義是告訴我們,很多事情只有願意、有耐心,就算在難的事情都能卻克服的,其實人生最困難的事情,並不是無法改變,而是自己能去培養一個好習慣並長期去執行它,來這也是古人所說的「知易行難」的道理,三年多前我因為長期工作的壓力,導致身體有點不舒服,有位醫生要我去游泳來改善這個毛病,而且至少要持續一年才會得到結果,於是我自己就開始每天早上開始去晨泳,從夏天跨過冬天到另一年的夏天,游泳的距離也從50公尺開始,最後已經可以游到了2500公尺,這樣的持續運動不僅改善了我的身體健康,也讓我體驗到了「人只要願意,沒什麼做不到的」。

如果我們能夠去好好運用下班後的時間,甚至去投入時間去學習與經營人脈,都是可以讓自己獲益良多,很多年前開始,我每週都會花一些時間去投入人資聯誼會社團的工作,而這一做就整整將近8年的時間,開始很多人都會覺得我很傻,沒事幹麼去做義工,還免費去帶一群熱誠,剛進入HR領域,卻沒有什麼經驗的年輕人,但是因為學習是相互的,我在他們身上學到了很多事情,而且我也得到廣大的人脈資源,這些收穫與成果都是到了現在才慢慢的開始收割。

我深信有學習習慣的人,絕對會長期領先其他人,這是在職場永遠不變的道理,所以自己要如何去好好規劃與擅用自己下班後的時間,是自己超越他人的最重要關鍵因素之一,記得不要再想太多了!「Just do it」!

What does VMWare Tools do?

What does VMWare Tools do?

I noticed that a few folks have done Internet searches on exactly this question. VMWare answers this in their manual for WMWorkstation 6.x (page 113-115):

VMware Tools is a suite of utilities that enhances the performance of the virtual

machine’s guest operating system and improves management of the virtual machine.

Installing VMware Tools in the guest operating system is vital. Although the guest

operating system can run without VMware Tools, you lose important functionality and

convenience.

When you install VMware Tools, you install:

VMware Tools service. The program file is called VMwareService.exe on

Windows guests and vmware-guestd on Linux, FreeBSD, and Solaris guests.

This service performs various duties within the guest operating system:

-Passes messages from the host operating system to the guest operating

system.

-Executes commands in the operating system to cleanly shut down or restart a

Linux, FreeBSD, or Solaris system when you select power operations in

Workstation.

-Sends a heartbeat to a VMware Server, if you use the virtual machine with

VMware Server.

-On Windows guests, grabs and releases the mouse cursor.

-On Windows guests, fits the guest’s screen resolution to the host’s and vice

versa.

-Synchronizes the time in the guest operating system with the time in the host

operating system.

-Runs scripts that help automate guest operating system operations. The

scripts run when the virtual machine’s power state changes.

The service starts when the guest operating system boots.

NOTE: The VMware Tools service is not installed on NetWare operating systems.

Instead, the vmwtool program is installed. It synchronizes time and allows you to

turn the CPU idler on or off. See “Using the System Console to Configure VMware

Tools in a NetWare Guest Operating System” on page 139.

A set of VMware device drivers. These drivers include:

-SVGA display driver that provides high display resolution and significantly

faster overall graphics performance.

-The vmxnet networking driver for some guest operating systems.

-BusLogic SCSI driver for some guest operating systems.

-VMware mouse driver.

-A kernel module for handling shared folders, called hgfs.sys on Windows

and vmhgfs on Linux and Solaris.

VMware user process. The program file is called VMwareUser.exe on Windows

guests and vmware-user on Linux and Solaris guests.

This service performs the following tasks within the guest operating system:

-Enables you to copy and paste text between the guest and host operating

systems, and copy and paste files between the host operating systems and

Windows, Linux, and Solaris guest operating systems.

-Enables you to drag and drop files between the host operating systems and

Windows, Linux, and Solaris guest operating systems.

-On Linux and Solaris guests, grabs and releases the mouse cursor when the

SVGA driver is not installed.

-On Linux and Solaris guests, fits the guest’s screen resolution to the host’s.

NOTE: The VMware Tools user process is not installed on NetWare operating

systems. Instead, the vmwtool program is installed. It controls the grabbing and

releasing of the mouse cursor. It also allows you copy and paste text. You cannot

drag and drop or copy and paste files between hosts and NetWare guest operating

systems.

VMware Tools control panel. The Tools control panel lets you modify settings,

shrink virtual disks, and connect and disconnect virtual devices.

So, in short, it significantly enhances the speed and efficiency of your guest machine. It also adds capabilities. VMWare strongly recommends that you install VMWare Tools in all of your virtual machines, and I fully agree with this recommendation.

Lastly, note that you must have the guest operating system installed and running in the virtual machine in order to install VMWare Tools. It literally installs itself into the guest operating system.

I hope that this helps some folks.

Wednesday, September 16, 2009

按表操課

按表操課

To see how long it takes a command to run

To see how long it takes a command to run, type the word "time" before the
command name.
-- Dru

MySQL 的調校 (軟硬體、版本、設定)

MySQL 的調校 (軟硬體、版本、設定)

Published

by

Gea-Suan Lin

on September 13, 2009

in Computer, Database, Murmuring, MySQL and Software. 0 Comments

把一些關於 MySQL 的資料整理一下。

初期的 MySQL 隨便跑沒關係,備份的部份記得要把 binlog 也一起備份起來,用 gzip 壓過後 (不使用 bzip2 或是高壓縮率參數,是因為考量解壓縮速度;另外推薦用 Parallel gzip 壓縮,速度比較快) 再用 openssl 加密丟到 Amazon S3 上。

成長後,買獨立伺服器要一次買兩台跑 HA,每台分別是:

CPU 要考量 SQL query 的方式,如果打算在 MySQL 做很多事情 (i.e. JOIN),CPU 要選高階的;如果大多都是 simple query,則以 C/P 值高的 CPU 優先:兩顆四核心 CPU 算是現在比較划算的硬體。不管哪一種,選低電壓的,像是 Intel Xeon L5408 或是 Intel Xeon L5520,因為硬碟蠻熱的,要減少熱量以免伺服器容易當掉。

記憶體愈多愈好,64GB 算是還蠻基本的。

硬碟選轉速快的 15krpm SAS,挑大一點的硬碟 (以現在的市場就是 300GB) 省得以後空間不夠要搬動。最好是硬體 RAID1+0,依照應用決定單台 database 要多大,如果預定八顆的話可以買 2U 來塞。

軟體的部份:

一定要跑 Linux x86-64 版本,挑大的 distribution 以免遇到問題卻無法解決。我自己還蠻偏好用 Debian。不論是 Debian 還是其他 distribution,儘量跟穩定的 branch,遇到需要升級時的問題會比較少,像是 Debian Lenny

如果要跑 DRBD,先在兩台上面設定好 Heartbeat + DRBD。如果是跑 MMM 的話就設定 MMM,比較需要注意的是 MMM 的版本,參考「MySQL MMM 的情況」。

Filesystem 跑 XFS,很多人在上面跑很久了,經過時間考驗的 Filesystem,跑起 MySQL InnoDB 的效率還不錯。

MySQL 跑 Percona 的 5.0 標準版本 (非 highperf 版),穩定性還不錯。如果預期到資料量很大的時候會是 I/O bound,可以考慮 Percona 的 5.1 版本,並且開啟 InnoDB Plugin 壓縮的功能。

跑監控程式,把系統的狀態記錄下來。可以是 MuninCacti 或是 nagios,資料對於瓶頸分析很重要。

my.cnf 設定的部份要花不少功夫,除了一般常見的設定外 (這部份網路上很多文件),有些在站台比較大時會發生的問題要注意:

back_log 要開大,因為站台大的時候通常不會用 pconnect (每個 web server 都掛著 64 個連線,當有十台 web server 就佔用 640 個連線),而是用 connect,在每次做完事情就斷線,配合 memcached 降低 MySQL 的需求。不過在量夠大的時候,還是會遇到預設的 back_log 不夠。Smugmug 的 CEO 在「Great things afoot in the MySQL community」有提到吃過這個值的虧。

max_allowed_packet 設大一點,避免比較大的 INSERT 或是 UPDATE 造成錯誤。通常這是設計上的問題,應該要避免在 MySQL 裡放 blob 資料,不過偶而還是會需要…

max_connect_errors 設 4294967295,可以避免當 client (像是 php) 發生太多錯誤時被 block 住。

innodb_adaptive_checkpoint 要打開,可以避免在 flush dirty pages 的時候產生 slow query。MySQL 官方的版本沒有這個參數,而這個參數也是為什麼要用 Percona 版本之一。效果可以參考「Adaptive checkpointing」這篇文章。

Tuesday, September 15, 2009

How to Read MySQL Binary Log Files (BinLog) with mysqlbinlog

How to Read MySQL Binary Log Files (BinLog) with mysqlbinlog

MySQL database server generates binary log files for every transaction to the databases, provided administrator does not disable or comment out the “log-bin” parameter in my.cny configuration file. The binary log files are written in binary format. Although the binary logs, or also known as logbin are mainly used for MySQL database replication purpose, sometimes you may need to examine or read the contents of binary logs in text format, where the mysqlbinlog utility will come in handy.

Binary log file, which normally has name with the format host_name-bin.xxxxxx and store in /var/lib/mysql directory, could not be opened and read straight away as it’s in unreadable binary format. To read the binary logs in text format, we can make use of mysqlbinlog command, which also able to readrelay log files written by a slave server in a replication setup. Relay logs have the same format as binary log files.

To use mysqlbinlog utility is simple, simply use the following command syntax to invoke mysqlbinlog after login in as root (else you have to specify user name and password) to shell via SSH:

mysqlbinlog [options] log_file ...

So to read and display the contents of the binary log file named binlog.000001, use this command:

mysqlbinlog binlog.000001

The binary log files and its data are likely to be very huge, thus making it almost impossible to read anything on screen. However, you can pipe the output of mysqlbinlog into a file which can be open up for later browsing in text editor, by using the following command:

mysqlbinlog binlog.000001 > filename.txt

To reduce the amount of data retrieved from binary logs, there are several options that can be used to limit the data that is been returned. Among the useful ones are listed below:

–start-datetime=datetime

Start reading the binary log at the first event having a timestamp equal to or later than the datetime argument. The datetime value is relative to the local time zone on the machine where you run mysqlbinlog. The value should be in a format accepted for the DATETIME or TIMESTAMP data types. For example:

mysqlbinlog --start-datetime="2005-12-25 11:25:56" binlog.000001

–stop-datetime=datetime

Stop reading the binary log at the first event having a timestamp equal or posterior to the datetime argument. This option is useful for point-in-time recovery. See the description of the –start-datetime option for information about the datetime value.

–start-position=N

Start reading the binary log at the first event having a position equal to the N argument. This option applies to the first log file named on the command line.

–stop-position=N

Stop reading the binary log at the first event having a position equal or greater than the N argument. This option applies to the last log file named on the command line.

For more usage information on mysqlbinlog, visit here

Monday, September 14, 2009

So lonely inside So busy out there And all you wanted was somebody who cares If you need me you know I'll be there

So lonely inside
So busy out there
And all you wanted
was somebody who cares
If you need me you know I'll be there

Sunday, September 13, 2009

鬥志

鬥志

自古以來當兩軍對峙,哪一方能夠勝出的關鍵要素就是「鬥志」,往往最後勝出的一方並不是能力最強,裝備最好的一方,原因在於戰鬥精神力的總和,可以底過對方好幾十倍的物質戰力,而人在職場競爭也是相同的,如果缺乏「鬥志」的一方,就算擁有更好的裝備與人員,一切還是惘然。

昨天公司的籃球比賽已經進入到最後的「半決賽」,也是爭奪冠亞軍的入場比賽,代表我們部門的籃球隊伍,在各級長官的鼓舞與勉勵下,很辛苦的從預賽一路打進了半決賽,沒想到在昨晚卻沒有全力以赴的表現,終場小輸了幾分,賽後所有球員沒有惋惜與難過的心情,都直說「嗯!不錯了」,事後我找領隊來談昨晚球賽的過程與結果,他很開心的告訴我「我們預期對方的實力很強,本來就只想拼第三名,沒想到對手也許因為輕敵,沒有把實力展現出來,我們還差點打贏這場球」,我很嚴肅的跟他說「如果我們有『鬥志』,以自己最大的努力來拼鬥比賽,我們可以抓住這個機會,進入冠亞軍總決賽了」。

多年前我在某間外商企業任職,有一次我到新加坡出差,參加公司亞太地區人資部門團隊的定期聚會,這次會議特別安排有個「Teambuilding」的活動,我剛好跟總部(Headquarters)的CHO(Chief HR Officer)也是我們人資副總「VP」(Vice-President),一起編在同一隊參加競賽,整個團隊活動的設計,是有好幾場的競賽,而我們這隊的成員平均年齡是很高的,而成員因為來自於不同的國家,所以溝通也很吃力,也許因為我們這一隊的運氣還不錯,第一次的競賽就贏了其他隊伍,當我們每個人都覺得很高興、自滿時,這位副總卻很嚴肅的跟全隊夥伴說「短暫的勝利只是僥倖,我們對於任何事情,永遠要抱持著堅毅的鬥志,要盡最大的努力去爭取勝利,但是心理不要把最後的得失看的太重」,我們全隊就在這位充滿鬥志的HR副總帶領下,進了最大的努力,也得到了比預期更好的成績。

如果我們去翻閱人類的戰史,就會發現自古以來當兩軍對峙,哪一方能夠勝出的關鍵要素就是「鬥志」,往往最後勝出的一方並不是能力最強,裝備最好的一方,原因在於戰鬥精神力的總和,可以底過對方好幾十倍的物質戰力,而人在職場競爭也是相同的,如果缺乏「鬥志」的一方,就算擁有更好的裝備與人員,一切還是惘然,終就還是打不贏對方,甚至最後還以慘敗來收場,我們常常都會聽到一句話「還沒打就輸了」,就是「輸」在團隊的「氣勢」,而整個團隊的氣勢就是來自於整體的「鬥志」,而如何去營造與提昇團隊的「鬥志」,就是考驗一位領導者的智慧與能力,如果我們的團隊充滿了鬥志,就會有那種「雖千萬人,吾往以」的氣概與士氣產生,那也就能有無堅不克的戰鬥力出現。

其實人在職場的競爭也是一樣的,就要像我們當年那位人資副總所說的「對於任何事情,永遠要抱持著堅毅的鬥志,要盡最大的努力去爭取勝利,但是心理不要把最後的得失看的太重」,畢竟當機會來的時候,誰能夠在瞬間抓住並展現成效,就能有成功的機會,而缺乏鬥志的人,則只能老是在旁邊自怨自哀、忌妒與怨恨別人的成就,不同的態度就決定了不同的命運。

Thursday, September 10, 2009

PDA 點餐系統

This is the html version of the file http://myiecs.iecs.fcu.edu.tw/upload/paper_uni/912pdf/910230.pdf.

Google automatically generates html versions of documents as we crawl the web.

Page 1

逢 甲 大 學

資訊工程學系專題報告

◎◎◎PDA點餐系統◎◎◎

◎◎◎◎◎◎◎◎◎◎◎◎

涂 允 勝 (四甲)



生: 黃 振 註 (四甲)

黃 川 泰 (四甲)

指 導 教 授 : 黃 秋 煌

中華民國九十二年四月

I

Page 2





























P

D

A









91

II

Page 3

III

Page 4

目錄

第一章、 緒論..............................................3

1.1、 動機.............................................3

1.2、 目標.............................................4

1.3、 系統簡介.........................................5

第二章、 背景..............................................6

2.1、硬體環境..........................................6

2.2、系統開發軟體......................................6

2.2.1 Visual Basic 6.0..............................6

2.2.2 Microsoft eMbedded Visual Basic 3.0...........6

2.2.3 資料庫的介紹..................................7

2.3、Winsock 介紹......................................17

2.3.1 建立 Server 端................................18

2.3.2 建立 Client 端................................19

2.4、無線網路介紹.....................................19

第三章、 系統架構.........................................22

3.0、資料正規劃與關連式資料庫說明.....................22

3.0.1、資料正規劃.....................................22

3.0.2、關連式資料庫...................................33

Page 5

1

3.1、系統架構.........................................36

3.1.1、Sever 端........................................37

3.1.2、Client 端......................................39

3.2、資料流程圖.......................................41

第四章、 系統實作.........................................43

4.1 Server 端.........................................43

4.1.1

Menu 資料庫..................................43

4.1.2

櫃檯的接收..................................55

4.2

Client 端.........................................62

第五章、 系統使用手冊.....................................75

5.1 PDA 點餐系統的建構.................................75

5.1.1

Server 端的建構..............................75

5.1.2

Client 端的建構..............................78

5.2 Server 端..........................................78

5.2.1 主畫面.......................................78

5.2.2

Menu........................................80

5.2.3

櫃臺........................................86

5.3 Client 端.........................................90

第六章、 結論與心得......................................107

Page 6

2

參考資料................................................113

附錄 A、.................................................115

附錄 B、.................................................146

附錄 C、.................................................185

Page 7

3

第一章、 緒論

1.1、動機

網路改變電腦的使用(LAN -> WAN ->Internet -> Wireless)。

檢視電腦帶給人們或社會最大的應用時,我們發現資料的溝通型

態,往往會帶來不僅電腦應用效益的改變,甚至社會工作型態與生活

方式的改變。當只有單一電腦時,在強大的運算功能或是資料庫管理

系統,也僅可以在定點為少數人所用,因此隨著區域網路的發展,電

腦成為企業組組織的工作的利器。然而網際網路更是將全世界的電腦

連接起來的大功臣,讓不管身在何處的人們,只要透過連上網際網

路,便可以與世界各地的電腦做溝通與資料交換。網路的連結其功效

便開始大大改變了工作方式,群組的工作溝通更加方便與有效率了,

人們的溝通互動不受地理的限制,生活與工作的模式也因網路可有所

改變。

無線上網環境逐漸成熟促成了行動商務

隨著無線上網的日益普及,無線通訊是網際網路另一波的革命浪

潮,擺脫了以往必須在定點上網的限制,無線上網讓工作與生活更具

Page 8

4

機動性,工作的進展將不再受制於時空環境設備的限制。在競爭日趨

激烈的數位時代裡,工作的即時性將越來越重要,速度與時間將成為

企業競爭的重要關鍵。行動工作的發展大致可分為以個人行動工作與

企業行動商務兩個發展主軸,輕薄短小結合無線通訊的 PDA 具有可攜

性與電腦的資料處理能力,帶動了所謂的「行動商務」發展。成為許

多企業下一波 e 化的重點。許多軟硬體資訊大廠的投入,都相繼宣布

投入行動商務服務的行列,宣告了行動商務的時代已經到來。

1.2、目標

目前大部分的餐廳仍是以傳統書面方式及定點式系統點餐,如果

餐廳的佔地較廣或正值用餐尖峰時間,則需花費較多的人力與時間往

返,而降低了服務的效率。為了解決此問題,打造無線行動點餐的環

境,提昇營運效率和服務品質。透過無線點餐系統達到即時、快速、

無紙化的點餐品質。無線點餐系統的行動性、可攜帶性帶來極大的便

利。透過 PDA 點餐系統,侍者點餐時只需要以畫面點選的方式,即時

記下客人的需求,經過確認後,透過無線網路將顧客所點的餐點傳送

到廚房與櫃檯。這當中,節省下侍者從餐桌走到廚房遞單、再移動至

櫃檯紀錄的時間,也縮短了顧客等候的時間。PDA 點餐系統是藉由行

動終端設備:PDA、PC 與無線網路設備,來達到工作人員可即時地進

Page 9

5

行點餐工作,再經由無線通訊方式傳輸至廚房;同時櫃台收銀區亦會

收到交易訊息,不需要額外的書面帳單的傳送、也不需要再將帳單重

新輸入,卻能讓多人同時分享資訊,可確實有效地提昇工作效率。特

別是在高峰時間,這套系統減少了不少人力與時間的支出,也降低了

點單錯誤的機率。而且,在客人的眼中,這套系統更留下了積極經營

的正面印象。

1.3、系統簡介

顧客的餐點經由無線區域網路設備,包含侍者手中的PDA、server

端的 PC、無線 AP、無線網路卡,將顧客的點餐傳送至廚房、櫃臺。

當顧客決定餐點的同時,廚師就可開始準備顧客已點用的餐點,不但

可節省時間快速上菜,提高顧客滿意度,還可降低點餐的錯誤,間接

降低時才成本,因為將菜單選項資訊電子化,透過侍者點選後加以確

認的方式,記錄顧客的需求,便減少了流程中因錯認侍者字跡、導致

廚房出單錯誤的機率。

Page 10

6

第二章、 背景

2.1、硬體環境

本組測試的硬體環境包含:

桌上型 PC 一組:作業系統 Windows2000、CPU800Mz、RAM512Mb。

PDA 一台:Ipaq3600、作業系統 Windows CE、CPU206Mz、RAM32Mb、

PDA 傳輸座。

無線網路:無線 AP、無線網卡 for PC and PDA。

2.2、系統開發軟體

2.2.1 Visual Basic 6.0

在 1965 年,大學教授 John G. Kemeny 以及 Thomas E. Kurtz 就已經

提出交談式學習程式語言的概念,為了這樣的需求,他們把當時最流

行的程式語言 Fortran 加以簡化,並且加入一些特點,創造出 Basic。

而隨著技術的演進,程式語言也由最基本的文字輸出輸入,演變成圖

形化的操作介面。於是微軟在 1991 年發表了 Visual Basic 1.0,提

供給 Windows 程式設計者另外一個選擇。如今的 Visual Basic 6.0

除了承襲了易學易用的特點之外,也新增了多媒體、資料庫、ActiveX

元件開發、Internet 程式開發等。

Page 11

7

2.2.2 Microsoft eMbedded Visual Basic 3.0

此程式語言專門針對開發 PDA 軟體而設計,eMbedded Visual Tools

可分為 eMbedded Visual Basic 3.0 以及 eMbedded Visual C++ 3.0。

本小組是採用 eMbedded Visual Basic 3.0 來設計 PDA 上的 Client

應用程式。eMbedded Visual Basic 3.0 可選擇要編寫 OS 程式或 Win

CE 程式。不僅可以藉由程式產生的 PDA 模擬器來測試軟體,也可直

接在 PDA 測試。編寫完的程式,可製作成一般安裝檔給其他相同系統

的 PDA 使用。

2.2.3 資料庫的介紹

2.2.3.1 資料庫基本概念

依資料結構的不同.資料庫可以被分類為關聯式.階層式.網狀式.

物件導向式等.而這些分類中又以關聯式資料庫最為大多數人所使用.

所謂的關聯式資料庫就是一種表格式(二維表格)的資料結構.依據資

料表的關聯(兩資料表間的一個連結)來做存取的動作.例如:當刪除

某一個客戶.資料庫可以依關聯找到客戶的訂單.一併刪除。

我們可以透過ADO.DAO.等資料控制項來操作.存取資料庫.同時.

類似一般控制像物件.對這些資料庫存取物件.仍可以適當地加入程

式碼.以增強其功能.我僅就資料庫結構的基本觀念與 SQL 指令的運

用加以探討.希望讀者對研讀更深入的資料庫課題會更有幫助。

Page 12

8

2.2.3.2 資料庫結構

資料庫的組織結構由上而下依序欄位(Field).紀錄(Record).資

料表(Table)而組成資料庫(Database).如表單所提菜單的資料庫中.

菜單的菜名,價格…等都屬於”欄位”.每道菜的欄位集合叫做一

筆”紀錄”.將這些同結構的紀錄集合起來.可以組成一個”資料

表”.一個或多個相關資料表又構成一個”資料庫”.而資料庫管理

系統(DBMS).便是用來管理資料庫的軟體.如 MS-SQL.Sybase.Oracle

等皆屬之。

(a) 欄位

每個欄位都會有個代表其欄位意義的名稱,在同一個資料表中,

欄位名稱是不可以重複的,欄位也會定義不同的資料型態,以便

存放各種資料。

(b) 紀錄

紀錄由多個欄位組成,如菜單的 table,每個紀錄就是一道菜,價

格及菜名就是其欄位,而每一道菜的紀錄就是由菜名和價格組

成。

(c) 資料表

資料表就是由相同的結構定義所組成,通常來表示某種分類如菜

單這個 table 就是一個資料表。

Page 13

9

2.2.3.3 開放資料庫連結協定(ODBC)

當我們要新增一筆資料至資料庫時.不同資料庫對於此一新增資料動

作的使用語法可能都不相同.但是在這種情形下.是否就意味著我們

必須針對不同的資料庫撰寫相對應的程式碼呢?

事實上.寫程式並沒有這麼麻煩.透過開放資料庫連結協定

(ODBC.Open Database Connectivity)當作我們存取資料庫的介面.

就可以輕易的存取不同資料庫.ODBC 是一種程式的”介面”.它讓應

用程式可以透過它存取支援”結構化查詢語言”(SQL)的資料庫資料.

當然.這樣的方便.必須透過 ODBC 驅動程式達成.也就是資料庫廠商

必須提供支援作業系統的ODBC驅動程式.舉例來說.如果我們以VB搭

配 Access 開發一個進銷存管理系統.突然有一天.我們想改用 MS-SQL

資料庫(例如資料量大到 Access 無法負荷或者速度的考量).透過

ODBC 的驅動程式.我們可以簡單幾個操作步驟將整個系統轉移.而不

必重新開發.這便是使用 ODBC 的特異功能.它將不同資料庫資料存取

的責任.都封裝到相對應的驅動程式.也就是說.ODBC只提供應用程式

與資料庫之間連結的介面.而它實際上如何處理(也就是實作).並不

需要程式設計者煩惱.因為.它是執行階段由資料庫那一端實作的.就

好像使用 Word 列印時.只要安裝.指定列印機的驅動程式這麼簡單

接下來.我們概要說明 ODBC 的架構.ODBC 包含應用程式.驅動程式管

Page 14

10

理者.驅動程式.資料來源四個構成要素。

[1]應用程式 (Application)

主要負責執行一個行程.選擇要連結的”資料來源”.傳遞 SQL 敘述

(透過呼叫 ODBC 函數).取得傳回結果.處理錯誤.交易確認與取消及

中斷連結資料來源。

[2]驅動程式管理者 (Driver Manager)

驅動程式管理者主要工作是將應用程式的 ODBC 函數呼叫傳遞到正確

的驅動程式.它也可以直接處理幾個 ODBC 函數呼叫及基本的錯誤檢

查.驅動程式管理者另一個重要的工作就是載入.載出驅動程式.前述

的應用程式.則只負責載入.載出驅動程式管理者。

[3]驅動程式 (Driver)

驅動程式用來實作 ODBC 函數.因此.當應用程式呼叫存取不同資料庫

的 ODBC 函數時.也需有不同的驅動程式來實作這些函數(功能).傳遞

SQL 指令的要求.或者傳回查詢結果給應用程式。

事實上.驅動程式最重要的功能之一.就是把應用程式接收的 SQL 指

令(透過 ODBC函數呼叫)轉換為指定資料庫看得懂的SQL命令.並傳遞

給相對應的資料來源。

[4]資料來源 (Data Sources)

資料來源可以是檔案.DBMS 的資料庫等.其主要目的是匯集應用程式

Page 15

11

存取資料庫所需的全部資訊(包括驅動程式名稱.IP 位置等).它也可

以是作業系統或網路平台上的一個資料庫管理系統。

我們針對 WIND0WS98 的 ODBC 資源設定解說吧。

首先點選 Windows 視窗下的[ 開始>設定>控制台>ODBC 資料來源

(ODBC Data Source) ].以便進入”ODBC 資料來源管理員(ODBC Data

Source Administrator)”視窗 (如下圖)

(圖 2-1)

在進入 ODBC 資料來源管理員視窗後.視窗中有三種與資料來源有關

的標籤頁可供設定”資料來源名稱”.分述如下:

[a]使用者資料來源名稱 (User DSN)

設定在這個標籤頁的資料來源只能提供給本機電腦使用.並糗只有目

Page 16

12

前的使用者才可以使用。

系統資料來源名稱 (System DSN)

設定在這個標籤頁的資料來源只能提供給本機電腦使用.不果只要是

在這個系統上或是其他具有存取權限的使用者都可以使用此資料來

源。

[c]檔案資料來源名稱 (File DSN)

設定在這個標籤頁的資料來源可以被安裝同一個驅動程式的所有使

用者使用.此種資料來源並不限定某一個使用者身上或某一個本機電

腦上.檔案資料來源並不以資料來源紀錄我們所設定的資料來源.而

是以檔案名稱紀錄。

前述三種資料來源.雖然使用對象不同.但設定的方式幾乎是一樣的.

因此.我們直接以”使用者的資料來源名稱”頁籤設定.說明如何新

增.刪除與修改聯結 MDB 格式資料庫檔案的資料來源.分述如下:

(一)新增資料來源名稱 (User DSN)

要在”使用者資料來源”名稱頁籤建立 MDB 格式資料庫檔案的連

結。其步驟如下:

步驟一:

再使用者資料來源名稱的標籤頁下點選”新增”鈕.進入”建立資料

來源(Create New Data Source)”視窗 (如下圖)

Page 17

13

(圖 2-2)

在進入”建立新資料來源”視窗後.選擇驅動程式名稱

為”Microsoft Access Driver (*.mdb)”.按”完成”鈕進入”ODBC

Microsoft Access 設定”視窗。

步驟二:

在”ODBC Microsoft Access 設定”視窗中.輸入資料來源名稱(名稱

可自訂.假設我們輸入 MyExDSN)並且選取要存取資料庫來源.要設定

資料庫來源.只要點選”選取(Select)”鈕.於蹦現出的”選取資料

庫”視窗.選擇所要的資料庫檔案(假設我們選擇範例光碟所附的

Data.mdb檔.並請確定此檔以複製到硬碟以及唯讀屬性已去除).完成

後按”確定(OK)”鈕返回 ODBC Microsoft Access 設定視窗 (如下

圖)

Page 18

14

(圖 2-3)

步驟三:

在”ODBC Microsoft Access 設定”視窗下點選”確定(OK)”鈕後.

回到”ODBC 資料來源管理員(ODBC Data Source Adminstrator)”視

窗.此時.在使用者資料來源的列表中.便會發現新增的資料來源名

稱.”db1”.已將顯示在使用者資料來源列表上了。

(二)移除資料來源名稱

當我們要刪除資料來源列表中.已存在的資料來源名稱.只要在 ODBC

資料來源管理員視窗內.選擇所要刪除的資料來源名稱.再點選”移

除(Remove)”鈕即可。

(三)變更資料來源名稱內容

要變更資料來源列表中已存在的資料來源.只需要在 ODBC 資料來源

Page 19

15

管理員視窗內.選擇要變更內容的資料來源.再點選”設定

(Configure)”鈕.就會回到新增時看到”ODBC Microsoft Access 設

定”視窗 (如上圖).與新增相同.包括資料來源名稱.資料庫與其他

選項都可以重新設定.完成後按”確定”鈕返回”ODBC資料來源管理

員”視窗。

2.2.3.4 SQL

結構化查詢語言(Structured Query Language)是一種對資料庫查

詢,存取與管理的標準語言,它使用一些簡單的音文字母句子組合

起來,相單簡單而且彈性高.SQL 既然是一種資料庫使用的語言,它

便會有一些規則.限制,比較糟糕的是,並非所有的資料庫管理系

統,都使用一模一樣的 SQL 指令.因此,要對資了庫下達某些叫特殊

的指令時,應該先參考資料庫技術手冊,了解它是否支援我們下達

的指令,或者該如何將指令修改為該資料庫接受的格式

以下是 SQL 的示範。

所需材料的 table

Page 20

16

(圖 2-4)

素材的 table

(圖 2-5)

我所使用的 SQL 敘述是用 join。

SQL 敘述:

select 所須材料.菜名,素材.素材名,素材.種類 From 所須材料,素

Page 21

17

材 where 所須材料.材料 = 素材.素材名

結果:

(圖 2-6)

2.3、Winsock 介紹

Socket 是一個網路的終點,結合了 IP 位址和通訊埠號碼。Socket 這

個字被用做一個隱喻,因為 socket 內所含的資訊可以直接插入目的

地電腦上被要求的處理程序中。Socket 可以用來識別 Intetnet 上任

何地方所執行的應用程式。

Page 22

18

Berkeley Socket(SBD Socket),是一個網路城市介面的名稱,已經

廣泛的被應用在 UNIX 中,作為 TCP/IP 的通訊工具。BSD Socket API

也可以運用在視窗上,稱為 Windows Sockets,或稱為 WinSock,

WinSock 包含了許多功能,以 Windows 為基礎的應用程式設計,Socket

是 Interner 程式設計可以做到的最低階方式。

Winsock Control 運作原理

程式設計師可以用 Winsock Control 元件,讓程式透過 TCP/IP 或紅

外線傳輸(IrDA)來傳遞資料。這個元件使用方式如下:

2.3.1 建立 Server 端

2.3.1.1 決定程式要當作 Server 端或 Client 端。假設要建立一個

TCP/IP 的 Server 端程式,先把 Winsock Control 元件的 Protocal

屬性設定成 sckTCPProtocal,並且把 Server 程式所要接受資料的

port 指定給 LocalPort 屬性。在設定好這些屬性之後,就可以呼叫

Listen 等待連接。

2.3.1.2 當有 Client 程式要連結到 Server 端的時候,Server 端的

Winsock Control 元件會產生 ConnectionRequest 這個事件,以

Accpet 來接受 Client 端的連線要求。

2.3.1.3 連線完成後,Winsock Control 的 State 屬性會被設定成

sckConnected,這時候就可以開始傳送資料。

Page 23

19

2.3.1.4 當資料傳到 Server 時,Winsock Control 元件,會產生

DataArrival 事件,通知程式有資料送達。如果要接受資料,使用

GetData 來接受該資料。

2.3.1.5 如果要使用 Winsock Control 元件傳送資料的話,使用

SendData,資料就會傳送至 RemoteHostIP 以及 RemotePort 這兩個屬

性所指定的地方。在傳送資料的過程中,會產生 SendProgress 事件,

傳送完畢之後,會產生 SendComplete 事件。

2.3.1.6 當要結束連線的時候,使用 Close 來中斷連線。連線中斷之

後,產生 Close 事件。

2.3.1.7 如果在上述過程中發生錯誤,Winsock Control 會產生 Error

事件,可將錯誤處理的程式碼放在此處。

2.3.2 建立 Client 程式

2.3.2.1 要建立用戶端程式,首先要設定好 RemoteHost 以及

RemotePort 屬性,並使用 Connect 來向 Server 要求連線。

2.3.2.2 連線建立之後,程式就可以使用 SendData 以及 GetData 來

傳遞或接收資料。

2.4、無線網路介紹

Page 24

20

隨著 IEEE802.11b 標準的制定,無線傳輸的速率最快可達到 11M

bps,加上主要網路設備廠商相繼投入,使得無線區域網路已漸漸受

到重視,大家都希望能透過無線網路連結原本有線區域網路,以增加

使用者的移動性及減少不必要的佈線需求。透過無線的方式雖然增加

了不少便利性,但是大家關心的是這種無線上網的方式是否容易管

理、擴充性如何及最重要的網路安全議題。

在無線區域網路的安全議題上面,主要包括處理的控制及資料傳

輸的隱密性兩個議題。在處理的控制方面是要讓只有授權的使用者能

處理重要的資料。隱密性則是要確保資料正確傳輸且不被竊取。由於

無線網路是使用 radio wave 方式在大氣中傳遞資料,所以架設無線

網路就好似到處裝設 Ethernet Ports,由於並沒有方向性或特定目

標,因此資料的保密性就成為很關鍵的重點。

一般無線區域網路要解決安全問題,通常是使用 SSIDs(service

set identifiers)及 WEP(wired equivalent privacy)。SSIDs 是在

插有無線網卡的終端設備上設定名稱,依此來判定處理的權限,而負

責接收並與一般區域網路連結的 Access Point 則會廣播其本身的

SSID,而這種 SSID 的安全性往往不足。而 WEP 方式是可以設定在傳

輸資料是以加密的方式進行,互相通訊的雙方使用相同的 key 及演算

Page 25

21

法,key 的管理就顯得格外重要。IEEE802.11b 標準定義兩種型式的

認證,open 及 shared key。Open key 是透過未加密的方式傳送 key

來認證,而 shared key 則是由 Access Point 先傳送一個封包給終端

設備,在由終端設備依照正確的 WEP key 來加密並回傳給 Access

Point,假如 key 值正確即通過認證。也有產品直接以網卡上的 MAC

位址直接做認證。

但目前方式也有些潛在威脅:由於 WEP 或 MAC 是與終端設備結

合,故需防止設備遺失或遭竊,若發生必須要能即時通知管理者,以

進行資料及靜態 key 值的修改並通知所有的接取設備;而 EP 認證方

式是單方向由 access point 對使用者來認證,有心人事可利用非授

權的 access point 來阻絕提供使用者服務。另外,WEP 是對每封包

加以封裝而非加以授權,駭客可能破壞資料封包。

Page 26

22

第三章、 系統架構

3.0 資料庫的正規劃與關連式資料庫的說明

3.0.1 資料正規化

何謂正規化

將表格細分成多個更小的表格,直到每個表格只描述一種事實為

止,這一連串的調整過程就稱為資料正規化(Normalization)。

3.0.1.1 目的

正規化的目的何在?簡單的說就是要將資料的重覆性降至最低

(避免資料重複的狀況發生)。倘若在不同的表格中都有學生的姓名

時,一旦有個學生改名了,則必須同步更改多個表格的內容;修改的

過程中若稍有遺漏,有些資料沒更正,就會發生不一致的狀況。因此,

避免資料重複是相當重要的。

3.0.1.2 步驟

第一正規化(First Normal Form,簡稱 1NF。由 E. F. Codd 提出)

第二正規化(Second Normal Form,簡稱 2NF。由 E. F. Codd 提出)

第三正規化(Third Normal Form,簡稱 3NF。由 E. F. Codd 提出)

Boyce/Codd 正規化(Boyce/Codd Normal Form,簡稱 BCNF。由 R. F.

Boyce 與 E. F. Codd 共同提出)

第四正規化(Fourth Normal Form,簡稱 4NF。由 R. Fagin 提出)

Page 27

23

第五正規化(Fifth Normal Form,簡稱 5NF。由 R. Fagin 提出)

3.0.1.3 實例探討

假設我們將要設計一個成績單郵寄列印系統,需要學號、地址、

郵遞區號、學科代碼與各科成績等資料,而初步搜集到的原始資料如

下表所示:

Stu_no City ZIP

Subject_no, Score

75312 台中市 400

(S5302, 89), (S5345, 90),

(S8005, 78), (S3581, 80),

(M1201, 65), (M5251, 95)

75524 高雄市 800 (S5302, 88)

75302 高雄縣 830

(S5302, 98), (S5345, 90),

(S3581, 84), (M5251, 85)

(表 3-1)

接下來,我們將依序探討 1NF, 2NF 與 3NF 的過程。

1. 第一正規化(First Normal Form)

條件

第一正規化的表格最重要的是能滿足「每個欄位只能含有一個值」這

個條件。

正規化

Page 28

24

原始表格:

Stu_no City ZIP

Subject_no, Score

75312 台中市 400

(S5302, 89), (S5345, 90),

(S8005, 78), (S3581, 80),

(M1201, 65), (M5251, 95)

75524 高雄市 800 (S5302, 88)

75302 高雄縣 830

(S5302, 98), (S5345, 90),

(S3581, 84), (M5251, 85)

(表 3-2)

在正規化之後,我們將表格命名為 A:

《A》

Stu_no City ZIP Subject_no Score

75312 台中市 400

S5302

89

75312 台中市 400

S5345

90

75312 台中市 400

S8005

78

75312 台中市 400

S3581

80

75312 台中市 400

M1201

65

Page 29

25

75312 台中市 400

M5251

95

75524 高雄市 800

S5302

88

75302 高雄縣 830

S5302

98

75302 高雄縣 830

S5345

90

75302 高雄縣 830

S3581

84

75302 高雄縣 830

M5251

85

(表 3-3)

結果討論

在同一學生只能選修同科目一次的條件下,「Stu_no」加上

「Subject_no」可以做為 A 的主鍵(Primary key)。我們以下圖來

說明主鍵與其他欄位之間在功能上的相依關係(Functional

Dependency):

(圖 3-1)

在 A 之中係以(Stu_no, Subject_no)為 Primary key,但從上圖

看來,有三項「功能相依」關係是錯誤的(如紅線所示),City 與 ZIP

的值與 Subject_no 絲毫無關。

Page 30

26

在這樣的架構下,將產生下列問題:

1. 無法單獨新增一筆學生資料。因為 Subject_no 是 Primary

key 之一,不能為空值(Null);因此,一個未修習任何課程學

生的資料,將無法寫入 A。

2. 無法單獨刪除一筆成績資料。如果我們打算刪除(75524,

S5302)這筆資料的話,該生的地址資料也將一併消失。

3. 需要同步異動的資料太多。假如 75312 這個學生搬家了,那麼

我們得異動其中的 6 筆紀錄。

2. 第二正規化(Second Normal Form)

條件

一個表格必須滿意第一正規化的條件,並且非主鍵的欄位都要對主鍵

有「完全地功能性相依(Fully Functional Dependency)」關係,才

能算是達到第二正規化。

正規化

已合乎 1NF 的表格 A:

Stu_no City ZIP Subject_no Score

75312 台中市 400

S5302

89

75312 台中市 400

S5345

90

Page 31

27

75312 台中市 400

S8005

78

75312 台中市 400

S3581

80

75312 台中市 400

M1201

65

75312 台中市 400

M5251

95

75524 高雄市 800

S5302

88

75302 高雄縣 830

S5302

98

75302 高雄縣 830

S5345

90

75302 高雄縣 830

S3581

84

75302 高雄縣 830

M5251

85

(表 3-4)

在正規化之後,我們將表格 A 一分為二,並分別命名為 B1 與 B2:

《B1》

Stu_no City ZIP

75312 台中市 400

75524 高雄市 800

75302 高雄縣 830

《B2》

Stu_no Subject_no Score

75312

S5302 89

75312

S5345 90

75312

S8005 78

75312

S3581 80

Page 32

28

75312

M1201 65

75312

M5251 95

75524

S5302 88

75302

S5302 98

75302

S5345 90

75302

S3581 84

75302

M5251 85

(表 3-5)

結果探討

在經過 2NF 之後,先前的「無法單獨新增一筆學生資料」與「無法

單獨刪除一筆成績資料」的問題都解決了。我們再看看 B1 與 B2 各

欄位和主鍵之間在功能上的相依關係(Functional Dependency):

(圖 3-2)

Page 33

29

(圖 3-3)

在一個表格中,如果某一欄位值可決定其他欄位值;而這些欄位中又

存在某一欄位可以決定剩餘欄位的值,稱為「遞移相依性(Transitive

Dependency)」。若有此一情況發生,在異動資料時,可能會造成其他

資料不一致的現象。

在 B1 之中便有「遞移相依性」關係存在:B1.Stu_no -> B1.City 且

B1.City -> B1.ZIP 。

在這樣的架構下,將產生下列問題:

1. 無法單獨新增一筆縣市資料。因為 Stu_no 是 Primary key,

不能為空值(Null);因此,若無任何學生居住的某個縣市,其

郵遞區號資料將無法被事先建立。

2. 無法單獨刪除一筆學生資料。如果我們打算刪除 75524 這筆資

料的話,該生所在的高雄市郵遞區號資料也將一併消失。

3. 仍有需要同步異動的資料。假如台中市的郵遞區號修改了,且

住在該地區的學生又不只一位時,那麼我們又得異動多筆紀錄

了。

Page 34

30

3. 第三正規化(Third Normal Form)

條件

一個表格必須滿意第二正規化的條件,並且消除「遞移相依」現象,

意即非主鍵的欄位之間沒有「完全地功能性相依」關係,才能算是達

到第三正規化。

正規化

已合乎 2NF 的表格 B1:

Stu_no City ZIP

75312 台中市 400

75524 高雄市 800

75302 高雄縣 830

(表 3-6)

在正規化之後,我們將表格 B1 再度一分為二,並分別命名為 C1 與

C2:

C1》

Stu_no City

《C2》

City ZIP

Page 35

31

75312 台中市

75524 高雄市

75302 高雄縣

台中市 400

高雄市 800

高雄縣 830

(表 3-7)

結果探討

在經過 3NF 之後,先前的「無法單獨新增一筆縣市資料」與「無法

單獨刪除一筆學生資料」的問題都解決了,需要同步異動大量資料的

情況似乎也不復存在了。我們再以表格與欄位間的相依關係來看看正

規化的結果:

《C1》

Stu_no City

75312 台中市

75524 高雄市

75302 高雄縣

《C2》

City ZIP

台中市 400

高雄市 800

高雄縣 830

《B2》

Stu_no Subject_no Score

75312

S5302 89

75312

S5345 90

75312

S8005 78

75312

S3581 80

75312

M1201 65

75312

M5251 95

Page 36

32

75524

S5302 88

75302

S5302 98

75302

S5345 90

75302

S3581 84

75302

M5251 85

(表 3-8)

(圖 3-4)

(圖 3-5)

(圖 3-6)

Page 37

33

一般表格進行至第三正規化時,多半沒有什麼狀況了;倘若仍有異常

狀況發生,則需繼續進行 Boyce-Codd 及第四、第五種正規化格式,

但實務上不常發生。

Boyce-Codd 正規化

將多個候選鍵中挑出一個決定因子作為主鍵。

4NF

去除多值相依性。

5NF

克服合併相依性。

正規化只是建立資料表的原則,而非鐵律。切莫因為過度正規化,

反而導致資料存取的效率下降。有時在優先考量執行效率的前提下,

我們還必須做適當的反正規化(Denormalize)。

(以上資料來源 http://chensh.loxa.edu.tw/php/B_2.php)

3.0.2 關聯式資料庫

3.0.2.1 資料

在日常生活中,我們會面對許多不同型態的資料,如親朋好

友的通訊資料、個人行程計畫等等。為了保存這些資料,我們會

自定一些規則,將它們有組織地記錄在紙張或電腦上,以便將來

取用。我們用這樣的方法長期保存資料,就算是一種「資料庫」。

Page 38

34

3.0.2.2 資料模型

前面提到,我們會自定一些資料記錄的規則,將資料庫以固定的

架構來組成;而用來表示資料庫如何組成的架構,稱為資料模型。

關於資料模型的理論不少,其中最為著名的是「關聯式資料模型」

(relational model of data),這是 E. F. Codd 博士(數學家,

IBM 的研究人員)於 1970 年在「A Relational Model of Data for

Large Shared Data Banks」這篇論文中所提出的。

這項理論隨後不斷地被討論與修正,到 1980 年前後開始有「關

連式」的資料庫產品上市;自此之後,資料庫方面的發展與研究

幾乎都是「關連式」的天下了。

3.0.2.3 關聯式資料庫

什麼樣的資料庫系統才是「關連式」的呢?簡單地說,「關連式系

統」就是:

1. 使用者看到的都是表格。

2. 使用者可使用的運算子,都是從舊表格中產生新表格。這些

運算子至少包括 RESTRICT(SELECT)、PROJECT 與 JOIN。

換句話說,「關連式系統」的特徵就是其利用表格來呈現資料,然

後將表格視為集合來進行處理。當要操作資料時,便是針對表格

Page 39

35

去執行以集合理論為基礎的數學運算,而其執行結果還是表格。

表格

下圖是一個「表格」(table),其中縱向的稱為「行」(column),

或是稱為「欄」(field),存放著相同性質的資料。橫向的稱為

「列」(row),或是「記錄」(record),裡頭包含許多不同性質

的資料項目。這個表格有 4 欄 3 列。

(圖 3-7)

圖中黑色名詞與紅色名詞的意義相近,雖然在大多數的場合它們

也被視為同義詞,但後者的定義較為嚴謹,才是符合數學理論的

正式名稱。

3.0.2.4 運算

關連式資料模型是以數學的集合理論為基礎,Codd 博士定義了

八個運算子(operator),用來對關聯式資料模型做數學運算。運

算後的結果會再度成為一個資料表(relations in, relations

Page 40

36

out),而且這個資料表也能再度進行運算。透過這樣不斷地運算,

便可導出所需的結果來。這八個運算子之中,前四個是傳統的集

合運算(union, intersection, difference, and Cartesian

product),後四個則是特殊的關聯式運算(restrict, project,

join, and divide)。

(圖 3-8)

舉個例子來看,從通訊錄中取出所有性別為「女性」的資料列,

Page 41

37

這就是「restrict」運算;若取出「姓名」與「電話」這兩欄的

所有資料,則屬於「project」運算。

(資料來源 http://chensh.loxa.edu.tw/php/B_2.php)

3.1、系統架構

系統基本架構包含:資料庫、Server 端與 Client 端(PDA),(圖 3-9)

可說明這三大主體的連結。Server 負責連結資料庫與 Client,並存

取資料庫裡的資料。Client 端,透過 Server 來讀取 Menu,作為讓顧

客點菜使用,顧客點好的菜單,Client 端送出命令給 Server,Server

接收之後傳送到櫃臺與廚房,讓廚房人員收到哪一桌,點了什麼菜,

由此資訊開始廚房的作業。

Page 42

38

(圖 3-9)

3.1.1 Server 端

Server 端,分為兩個最主要的部分,Menu 跟 Menu 的接收部分。

Server 端還負責確認使用者登入,判斷是否是存在的使用者,

Menu 的部分,採用 Microsoft Access 建立,包含幾個資料表:

pass 資料表:使用者的名稱與密碼。

VIP 資料表:顧客姓名、聯絡電話與優待折數。

素材資料表:各餐點所需的基本材料。

餐點資料表:包含各種餐點的單品名稱、價格。

Menu 的接收部分,主要是負責接受 Client 傳送的點餐資料。將所送

達的點餐資料,顯示給廚房人員觀看,讓廚房人員知道哪一桌,點了

Page 43

39

什麼菜色。

3.1.2、Client 端資料流程圖

(圖 3-10)

Page 44

40

(圖 3-10)顯示出結帳的資料流程圖,先決定欲結帳的桌次,讀取該

桌次的資料,所點的餐點明細、金額。決定折扣與否,確定之後,將

此消費記錄存入。

(圖 3-11)為一般人工作業、辦自動化點餐作業與自動化點餐作

業的比較圖。其中有幾點主要的差別。

1. 人工作業所有的動作,包含點餐、菜單的紀錄與遞送,都透過人

力來完成。

2. 辦自動化點餐作業,點餐與菜單的紀錄由人力完成,而菜單的傳

遞,則透過電腦傳輸。可節省往返廚房與櫃臺的時間。

3. 全自動化點餐作業,點餐、菜單的紀錄與傳遞、都透過電腦來傳

遞資料,服務生只需要做確認即可。不需要來回往返廚房、櫃臺,

尤其佔地較廣的餐廳,可以藉此省下更多的時間成本。

Page 45

41

(圖 3-11)(摘錄自”電腦科技雜誌”)

3.2、資料流程圖

(圖 3-12)為 Menu 的資料流程圖,作業功能說明:

1. 進入 Menu 頁之後,會先顯示出各餐點的資料,經由使用者動作,

Page 46

42

可進入 Menu 的新增、編輯、刪除、修改或者離開該頁面。

(圖 3-12)

Page 47

43

第四章、 系統實作

4.1 Server 端

4.1.1 Menu 資料庫

資料庫的是用關聯式資料庫,關聯式資料庫主要有下面幾個原則

(資料來源 http://www.dyu.edu.tw/~msung/OfficeAuto/Access/

Ref_Integrity.htm )

1.

在資料庫中形成關聯的兩個資料表格中之間的維護關

係,Access 藉由設定永久性關聯來達成。

2.

可將所有分散的資料整合在一起﹐應用於查詢、印表、統

計等工作上

3.

資料庫關聯圖視窗主要用來建立、檢視、及修改各資料表

間的永久性關聯。

資料庫有正規化, 正規化就是在資料庫內,相同的資料可由不同

的方式儲存。資料庫表格的設計會影響到未來資料管理、更新、

使用上的效率。正規化就是確保資料庫效率所發展的技巧。以一

步步正規化組織之檔案資料以設計資料庫之方法,可以克服在

update、insert、delete 時可能發生之問題。我是用 2NF 的方

式去做的,以下是我的資料庫格式。

Schema

Page 48

44

Table 1

帳號密碼表(pass)

欄位 欄位名稱

資料型態

Null 額外資訊

ID User Varchar

No 主鍵

PWD Password

Varchar

No

===========================================================

Table 2

菜單

欄位 欄位名稱

資料型態

Null 額外資訊

Name

菜名

Varchar

No

主鍵/排序

Price 單價 Int No

===========================================================

Table 3

訂單

欄位 欄位名稱

資料型態

Null 額外資訊

Name 菜名 Varchar

No

Amount 數量 Int

No

Page 49

45

Table 4

材料

欄位 欄位名稱

資料型態

Null 額外資訊

Name 素材名 Varchar

No 主鍵

Kind 種類 Varchar

No

Amount 數量 Int

No

Table 5

所需材料

欄位 欄位名稱

資料型態

Null 額外資訊

MenuName 菜名

Varchar No

主鍵

Name 素材名 Varchar

No

===========================================================

Table 6

套餐

Page 50

46

欄位 欄位名稱

資料型態

Null 額外資訊

Num 編碼 Int No

Name 菜名 Varchar

No 主鍵

Price 價格 Int No

===========================================================

Table7

點心

欄位 欄位名稱

資料型態

Null 額外資訊

Num 編碼 Int No

Name 菜名 Varchar

No 主鍵

Price 價格 Int No

===========================================================

Table 8

湯類

欄位 欄位名稱

資料型態

Null 額外資訊

Num 編碼 Int No

Name 菜名 Varchar

No 主鍵

Page 51

47

Price 價格 Int No

Table 9

菜單種類

欄位 欄位名稱

資料型態

Null 額外資訊

Name

菜名

Varchar

No

主鍵/排序

Kind 種類 Varchar

No

===========================================================

Table 10

顧客資料(vip)

欄位 欄位名稱

資料型態

Null 額外資訊

Name

菜名

Varchar

No

主鍵/排序

Price 單價 Int No

我是使用 2NF 但不是 3NF,因為我不能單獨對一表格的單一資料作處

理及新增刪除等動作(因為資料有「遞移相依性(Transitive

Dependency)」) 。

2NF 有啥缺點?請看下一段程式碼那是一個新增菜單的程式碼

新增程式碼:

Page 52

48

Private Sub cmdAdd_Click()

Dim kind As String

Dim num As Integer

Dim rmsg As String

If textadd1.Text = "" Or textadd2.Text = "" Or textkind1.Text

= "" Or Text3.Text = "" Or Text4.Text = "" Then

Beep

rmsg = MsgBox("請輸入要菜名及價格", vbOKOnly, "訊息")

Exit Sub

End If //確定新增菜的資料都有輸入

Set cc3 = New ADODB.Connection //以下皆為打開菜單種類資料流

的動作

Set rr3 = New ADODB.Recordset

cc3.ConnectionString = "dsn=db1"

cc3.Open

rr3.Open "菜單種類", cc3, adOpenStatic, adLockOptimistic

rr3.AddNew

//新增菜單在菜單種類上

rr3!菜名 = textadd1.Text

rr3!kind = textkind1.Text

Page 53

49

rr3.Update

kind = textkind1.Text

rs.AddNew //新增菜單在菜單上

rs!菜名 = textadd1.Text

rs!kind = textkind1.Text

rs!價格 = CLng(textadd2.Text)

Set cn0 = New ADODB.Connection

Set rs0 = New ADODB.Recordset

cn0.ConnectionString = "dsn=db1"

cn0.Open

rs0.Open "所須材料", cn0, adOpenStatic, adLockOptimistic//

以下皆為打開所需材料資料流的動作

rs0.AddNew //新增所需材料

rs0!菜名 = textadd1.Text

rs0!材料 = Text3.Text

rs0.AddNew

rs0!菜名 = textadd1.Text

rs0!材料 = Text4.Text

rs0.Update

Page 54

50

Set cn1 = New ADODB.Connection //以下皆為打開各個種類菜單

的資料流動作

Set rs1 = New ADODB.Recordset

cn1.ConnectionString = "dsn=db1"

cn1.Open

rs1.Open kind, cn1, adOpenStatic, adLockOptimistic

rs1.MoveLast

num = rs1!編碼

num = num + 1

rs1.AddNew //新增菜在各類菜單

rs1!編碼 = num

rs1!菜名 = textadd1.Text

rs1!價格 = textadd2.Text

rs1.Update

Set cc2 = New ADODB.Connection

Set rr2 = New ADODB.Recordset

cc2.ConnectionString = "dsn=db1"

cc2.Open

rr2.Open "訂單", cc2, adOpenStatic, adLockOptimistic

Page 55

51

rr2.AddNew

rr2!菜名 = textadd1.Text

rr2!數量 = "0"

rr2.Update

End Sub

以下是執行結果當我新增一筆菜單時我的 table 會有很多變動

如我新增一筆菜單像是海陸大餐

(圖 4-1)

Page 56

52

結果有 4 個 table 增加此菜

(圖 4-2)

(圖 4-3)

(圖 4-4)

Page 57

53

(圖 4-5)

可以注意到我的程式碼中有很多的動作都是在打開 table 的動作,

因為我用 2NF 的正規化,並沒有用到 3NF。這樣再做一個動作就必須

做很大的同步處理我沒用 3NF 的正規化的原因是因為我當初想要做

各各表單的 show 出表現而以並沒想想到要做的資料處理簡便,這是

我當初所沒想到的,而且每個表單的都有相關性。

做完這個資料庫的時候讓我知道下一次要做資料庫至少要做到3NF,

要不然會有很多的時間及系統資源都在做同步處理,而且在程式

debug 時就不會太麻煩,因為我在寫這個程式時最麻煩的就是再新增

及修改菜單時所花費的處理錯誤的時間上, 如果用 3NF 的話我想會

對程式的撰寫方面姐化很多,因為只要把每個動作做的沒問題就好,

Page 58

54

不需要對各個表單做檢查。

4.1.2 櫃檯

Server 端,採用 Visual Basic 6.0 編寫。在這個功能選

項底下,有一個建立新專案選項,選取之後,建立一個一般執行檔專

案,會自動產生一個新的 Project,並產生一個 Form1(圖 4-6)。

(圖 4-6)

在(圖 4-6)Form1 的畫面,可以再增加許多 Objects,如 Command

Buttom 或 Label,選取某一個物件,例如選取 Command Button 之後,

可由畫面右下角的屬性欄,建立修改該物件的資料。例如這個Command

Button 的顏色、字體、Caption、外框大小、畫面的位置之類的基本

設定。如(圖 4-7)

Page 59

55

(圖 4-7)

雙擊 Command Button,會出現另外一個新的視窗,Project-Form1

(程式碼)的視窗,可在這個視窗編寫程式碼來執行所要求的動作,而

一般而言,會將這個物件預設成 private 物件,僅供這個 Form 使用,

如(圖 4-8)所示。

Page 60

56

(圖 4-8)

新增一個表單,在(圖 4-6)的視窗,畫面右邊的部分,有一個專案

-Project 欄位,此欄位底下的表單上按右鍵,新增另外一個新的表

單,可選取一般表單,或是系統提供的特殊表單。例如選擇一個登入

對話方塊,就可以產生出一個使用者登入的對話方塊,如(圖 4-8)所

顯示。

Page 61

57

(圖 4-8)

而這個表單的好處在於,VB 6.0 會自動初始所需的基本程式碼,並

在程式碼中給予相關的說明。只需在之中,填入適當的程式碼,例如

使用者資料的連結、呼叫,判斷使用者資料輸入之後的動作。如(圖

4-9)所顯示

Page 62

58

(圖 4-9)

而 Menu 資料的讀取,與 Client 的資料接收,透過 Server 來運

作。採用網路連結的方式,在此,採用 Winsock 來實作。Winsock 負

責接收 Client 資料,此系統預設的 port 為 7577,而 IP 位址則是網

卡的 IP 位址。為什麼此系統還要有 port 跟 IP 位址呢?因為一個運用

到網路連結的程式,若沒指定其目的 IP,發送端無法決定資料的傳

送地點,如此一來,資料根本無法送達目的。而 port 只是給予一個”

通道”,例如一般 FTP port 為 21,POP3 port 為 110,Web port 為

80,如果 port 錯誤了,即使 IP 位址正確,資料還是無法傳送。採用

Winsock,Port 的指定由程式設計者指定,而 IP 位址會由 Winsock

決定,可以是一般網卡的 IP 位址,或是無線網路卡的 IP 位址。

Page 63

59

Client 端,在登入畫面,必須輸入正確的 IP 位址與 port,不然

無法連線到 Server,Server 端並不會顯示出連線狀態,連線是否成

功,由 Client 端來判定,若連線成功了,Client 會出現”連線開啟”

的訊息,若連線失敗,則出現連線失敗的錯誤訊息。Server 端只負

責接收 Client 端發出的命令,並顯示出接收到的資料。

(圖 4-10)包含幾個主要部分,第一個部分為 Menu 的顯示,這段

程式碼,會自動讀取此點餐系統設定的 ODBC 來源,按照順序顯示出

該餐點的名稱,餐點種類,與該餐點的價格。

以下為菜單顯示的程式碼片段:

Private Sub DataGrid1_Click()

End Sub

Private Sub Form_load()

Dim mysqo As String

mysqo = "select μa3a.μa¥W ,μa3a.Kind ,μa3a.»uRa From μa3a

order by μa3a.Kind asc"

Winsock1.Protocol = sckTCPProtocol

Winsock1.LocalPort = 7577

Page 64

60

Winsock1.Listen

Set cn3 = New ADODB.Connection

Set rs3 = New ADODB.Recordset

cn3.ConnectionString = "dsn=db1"

cn3.Open

rs3.Open mysqo, cn3, adOpenStatic, adLockOptimistic

Set DataGrid1.DataSource = rs3

End Sub

結果如下所示:

(圖 4-10)

Page 65

61

第二個部分在顯示出接收到的菜單,顯示順序包含餐點順序、桌

次、菜名、價格,會對每一次收到的菜單做排序,結果如下:

(圖 4-11)

(圖 4-11)畫面 1 的部分為利用 PDA 模擬器傳送的結果,由於模擬器

本身無法顯示中文字碼所以在顯現的時候,會出現??。

(圖 4-11)畫面 2 的部分是直接透過 PDA 傳送資料,在此顯現的資料

即可正常顯示。而 PDA 共傳送兩次資料,所以第二次收到的資料,點

餐順序還是由 1 開始排序。

4.2 Client(PDA)端

在系統實作方面 ﹐PDA 跟 SERVER 端的連結傳輸部分 ﹐我們使

用 WinSock 來撰寫程式 。

Page 66

62

WinSock 簡介 ﹕Winsock 是在 1991 年於美國加州的一次會議

中 ﹐由 JSB 公司的 Marting Hall 所提出的 。Windows Sockets 不

是指任何具有實體的程式或軟體,而是指一套公開的,在 MS Windows

下發展網路程式的應用程式介面(Application

ProgrammingInterface,API)。

(索引 ﹕

http://www.cis.nctu.edu.tw/~is82232/NetProg/program5.html#A

)

WinSock 的觀念 ﹕WinSock 應用程式對電腦使用者提供資訊服

務,它們讓使用者快速又容易的在電腦之間傳送資訊,在 WinSock 的

網路模型中,網路系統只單純的依 WinSock 應用程式的要求收送資

料,這些資料對 WinSock 應用程式而言是有意義的,對網路系統而言

就沒有,當 WinSock 應用程式送出一段具有特定長度、格式和意義的

資訊,網路系統可將這份資料任意地分割,然後在另一端重組回來,

它可能會將資料當作一串位元組,並要求應用程式在收到後將它們重

組回來,這些資料的處理方式會依所要求的傳輸服務而有不同,但無

論在何種情況,網路系統在傳送資料時都不會理會它的內容或意義 。

(索引 ﹕

http://www.mi.chu.edu.tw/~ta87859/WinSock/WinSock-2.htm)

Page 67

63

Winsock 的 I/O 模式共分為以下三種:

1、Blocking Mode

2、Nonblocking Mode

3、Asynchronous Mode

以下分別介紹:

Blocking Mode:在此模式下的函式呼叫後,會等到函式作用完

成之後才會返回函式呼叫點。函式作用可能需要等待某個時機才能完

成,而這個時機不一定會在該函式呼叫時就會出現。函式呼叫可能馬

上就能完成,也可能要經過一段相當長的時間。舉例,在阻攔模式下

用 recv() 接收遠端傳來的資料,你不能控制遠端程式傳資料過來的

時機,資料可能一下就丟過來,也有可能要等很久才會送過來。

Nonblocking Mode:在此模式下的函式呼叫,函式作用不管有沒

有完成,會馬上返回函式呼叫點。不像 blocking mode 下需要等待

函式完全作用完成後才會返回;在此模式下,目前等不到成熟的時

機,可以先做其它的事,回頭再來呼叫函式。舉例,在此模式下利用

recv() 去接收遠端傳來的資料。程式呼叫 recv() 接收資料,但是

因為網路塞車,資料無法及時傳來,所以這次的呼叫收不到任何的訊

息。程式返回呼叫點之後,告訴程式有關資料尚未收到的訊息。知道

Page 68

64

目前還有資料尚未收到,程式可以先做其它的事情,回頭再來呼叫

recv() 收其它的資料。

Asynchronous Mode:在此模式下,事件的發生是由某個判斷事

件發生的機制去通知應用程式,而應用程式依照該事件的性質坐不同

的處理。舉例,以此模式的 socket 接收遠端傳來的資料,傳送期間

不管經過了多少波折,資料送來了,由系統通知程式資料送達的訊

息。這時程式就可以去呼叫 recv() 函式接收資料。在此模式下,把

判斷函式呼叫的時機的責任交給系統(WinSock.DLL)。

(索引﹕

http://www.cis.nctu.edu.tw/~is82232/NetProg/program5.html#A

)

在使用 WinSock 物件方面十分的順利 ﹐因為在我們參考的書籍

(附錄中)裡面有提到滿多有關的使用方式 ﹐讓我們受益良多 。

PDA 程式範例 ﹕

Private Sub cmdConnect_Click()

WinSock1.Protocol = sckTCPProtocol

WinSock1.RemoteHost = txtRemortHost.Text

WinSock1.RemotePort = txtRemortPort.Text

Page 69

65

WinSock1.Connect

lblStatus.Caption = "連線開啟"

//*成功開啟連線才會出現 ﹐無法開啟會產生錯誤 。 處理方式在操

作簡介有作介紹 。*//

If WinSock1.State sckConnected Then

lblStatus.Caption = CStr(GetState(WinSock1))

Exit Sub

End If

cmdConnect.Enabled = False

cmdClose.Enabled = True

End Sub

//*此段程式是在開啟 WinSock 物件和 SERVER 端的連結 。*//

Private Sub cmdClose_Click()

Page 70

66

WinSock1.Close

WinSock1.LocalPort = 0

cmdConnect.Enabled = True

cmdClose.Enabled = False

lblStatus.Caption = "連線關閉"

End Sub

//*這段程式碼是關閉連線部分。*//

接下來 PDA 的重頭戲就在菜單資料庫的顯示 ﹐在參考許許多多

的書籍以及尋找網路上的資料 ﹐我們決定使用 GridCtrl 這項工具 ﹐

它可以把資料庫的內容讀取之後秀出在它的介面上 ﹐剛好對於我們

菜單的顯示有莫大的幫助 ﹐可以免除為了菜色的排版而大費周章 ﹐

不僅節省功夫而且效果非常的好 。

秀出菜單資料的主要程式碼 ﹕

Private Sub DisplayDataToGrid(tablename As String)

Dim rs As ADOCE.Recordset

Dim intCount As Integer

Dim strData As String

Page 71

67

Dim intCol As Integer

Dim intCounter As Integer

grdItems.Rows = 0

grdItems.AddItem ("餐點" + vbTab + "價格")

intCounter = 0

intCol = 0

For intCol = 0 To 1

grdItems.Col = intCol

grdItems.Row = intCounter

grdItems.CellFontSize = 10

Next

If connOpen = True Then

Set rs = CreateObject("ADOCE.Recordset.3.0")

On Error Resume Next

Page 72

68

rs.Open "select * from " & tablename, conn,

adOpenForwardOnly, adLockReadOnly

rs.MoveFirst

Do While Not rs.EOF

strData = CStr(rs(1).Value) + vbTab + CStr(rs(2).Value)

grdItems.AddItem strData

intCounter = intCounter + 1

For intCol = 0 To 1

grdItems.Col = intCol

grdItems.Row = intCounter

grdItems.CellFontSize = 10

Next

rs.MoveNext

Page 73

69

Loop

rs.Close

Set rs = Nothing

On Error GoTo 0

End If

grdItems.TopRow = 0

grdItems.ColWidth(0) = 2000

grdItems.ColWidth(1) = 1000

connClose

End Sub

//*這段程式碼是將資料庫內的資料依序讀出。*//

對於資料庫是否存在 ﹐有個別的測試程式存在 。

Page 74

70

測試程式 ﹕

Private Function DBExists(strFileName As String) As Boolean

If FileSystem1.Dir(strFileName) "" Then

DBExists = True

Else

DBExists = False

End If

End Function

如果存在將會將 DBExists 值傳回 ﹐根據不同的值 ﹐會有不同

的資訊顯示在 PDA 下方的 LABEL 上 。 (於操作介面說明有介紹。)

在點餐的方面 ﹐使用點擊方式來點餐 ﹐用指標來指定每一個欄

位的餐點 、價格 ﹐將依序排入 ARRAY 中 ﹐為的是可以將點餐的東

西秀到點餐清單的 LABEL 上 ﹐使用這方式可以準確的紀錄每一筆點

餐的菜色 、價格 ﹐使得在點餐確認作業上可以更精確更快速的進行



刪除的主要程式碼 ﹕

上略

Page 75

71

temp = MsgBox("你確定要刪除:" & ItemName1.Caption, vbOKCancel,

"刪除餐點")

If temp = vbCancel Then

Exit Sub

End If

intCurrentItemNo = (intCurrentPageNo - 1) * 8 + 1

intTotalItem = intTotalItem - 1

lblTotalCount.Caption = intTotalItem

lngTotalMoney = lngTotalMoney -

intItemMoney(intCurrentItemNo)

lblShopMoney.Caption = lngTotalMoney

If intItemCheck(intCurrentItemNo) = 1 Then

intTotalSelectItemNo = intTotalSelectItemNo - 1

lblBuyCount.Caption = intTotalSelectItemNo

End If

Page 76

72

MoveDate intCurrentItemNo

下略

MoveDate 函式的主要程式碼 ﹕

Private Sub MoveDate(ItemNo As Integer)

Dim intTemp

For intTemp = ItemNo To intTotalItem

strItemName(intTemp) = strItemName(intTemp + 1)

intItemMoney(intTemp) = intItemMoney(intTemp + 1)

intItemCheck(intTemp) = intItemCheck(intTemp + 1)

Next

strItemName(intTotalItem + 1) = ""

intItemMoney(intTotalItem + 1) = ""

intItemCheck(intTotalItem + 1) = 0

ClearList

Page 77

73

DisplayListData intCurrentPageNo

End Sub

在點餐清單上 ﹐我們使用 LABEL 將每一筆菜色 、價格條列出

來 ﹐也提供一個 CHECK BOX 來作標記使用 ﹐使用方法在操作介面說

明有解說 ﹐本來可以將每樣菜色都堆疊在一起 ﹐例如 ﹕牛排 5客 ﹐

這類的方法 ﹐但是考慮到刪除的時候 ﹐如果刪除 5 筆中的 1 筆 ﹐

就必須多個數字鍵輸入 ﹐為了求畫面的整齊 、清爽 ﹐所以我們將

每一筆資料都是個別的分開 ﹐不把相同的菜色累加在一起 ﹐在這方

面應該算是創意部分的不同 ﹐也許有更好的處理方式 ﹐有待我們去

發掘。

Page 78

74

第五章、 系統使用手冊

5.1 PDA 點餐系統的建構

5.1.1Server 端的建構

安裝 Server 端主程式,執行 PDA 點餐系統.exe 檔,會自動解壓縮並

產生”C:\Program Files\PDAShopping\”這個目錄,

設定 ODBC 資料來源,[開始]->[設定]->[控制台]->[系統管理工

具]->[資料來源(ODBC)]

進入系統的[控制台]選項。(如圖 5-1)

(圖 5-1)

選取[控制台]中的[系統管理工具]。如(圖 5-2)

Page 79

75

(圖 5-2)

選取[系統管理工具]中的[資料來源(ODBC)]選項。如(圖 5-3)

(圖 5-3)

新增一個資料來源。如圖(5-4)

Page 80

76

(圖 5-4)

選取 Driver do Microsoft Access(*.mdb),(圖 5-5),完成之後

(圖 5-5)

(圖 5-6)選取資料檔來源,輸入資料來源名稱”db1”

(圖 5-6)

選取資料庫名稱,位於 C:\Program Files\PDAShopping\db1.mdb

按下確定,即可設定此程式所使用的資料庫來源。如(圖 5-7)

Page 81

77

(圖 5-7)

5.1.2 Client 端的建構

將光碟放入光碟機中,選取 CD1 資料夾,雙擊 Setup 執行檔,即

可安裝 PDA 的 Client 端程式於 PDA 上。

5.2 Server 端

5.2.1 主畫面

(圖 5-8)

Page 82

78

執行主程式之後,會出現此視窗(圖 5-8),選取,可進入 PDA

點餐系統,或者選擇結束此系統。進入系統時,必須先做使用

者登入的動作(圖 5-9),

(圖 5-9)

輸入使用者名稱與密碼,若資料正確,即可登入主功能頁(圖 5-10)

。若使用者名稱輸入錯誤,會出現”sorry no this user”的錯誤訊

息,並結束此程式。若使用者名稱正確,密碼輸入錯誤,會出

現”password error”錯誤訊息,可再重新輸入密碼,或選擇取消,

離開程式。

主功能頁:

Page 83

79

(圖 5-10)

主功能頁包含,以及兩大功能。

5.2.2 MENU

選擇之後,可進入 Menu 的畫面

Page 84

80

(圖 5-11)

此頁面(圖 5-11)會顯示出 Menu 的資料。可進而對此 Menu 做,

,,。

(一) 首先是新增

初始畫面

Page 85

81

(圖 5-12)

按下的結果

Page 86

82

(圖 5-13)

(二)刪除

執行結果

Page 87

83

(圖 5-14)

按下的執行結果

Page 88

84

(圖 5-15)

(三)修改

初始畫面

(圖 5-16)

注意(圖 5-16)表格中的箭頭是說要改的資料

按下後

執行結果

Page 89

85

(圖 5-17)

5.2.3 櫃檯

在主功能頁(圖 5-10),選取之後,可進入櫃檯系統(圖 5-18)。

Page 90

86

(圖 5-18)

(圖 5-18)畫面中分為兩個顯示的部分,其中第一個部分,是顯示 Menu

的資料。第二個部分,顯示接收的菜單。

點選,會出現下圖(圖 5-19)。

Page 91

87

(圖 5-19)

此頁面會顯示出目前各餐點所需的基本素材。

選取,會出現下圖(圖 5-20)。

Page 92

88

(圖 5-20)

統計目前已點過的所有餐點。

Page 93

89

5.3 Client 端的操作說明

(圖 5-21)

連線區的起始畫面 ﹐伺服器 IP 和連接阜可以配合 SERVER 端更

改 ﹐桌號也可以依照不同的餐桌來做修訂 ﹐在沒有連線成功的畫面

時 ﹐下方有 LABEL 顯示連線狀態 “尚未連線” ﹐最下方的資料庫

準備就緒 ﹐是測試菜單的資料庫連結是否成功 ﹐成功則會顯現出

“資料庫準備就緒” 。 而在此畫面有 TABSTRIP 用來顯示三個大選

單 ﹐分別是 “連線區” 、”選擇餐點” 、”點餐清單” 。

將此頁擺在第一頁面 ﹐是為了方面服務生在點餐前可以確認一

些細節部分 ﹐例如 ﹕伺服器 IP 、連接阜 、是否有修改 ﹐隨著店

家喜好這些部分都是可以更改 ﹐以方便為主 。

Page 94

90

桌號的確認十分重要 ﹐為了要給顧客更快速的服務 ﹐桌號千萬

不能夠錯誤 ﹐不然就沒辦法達到使用 PDA 點餐的用意 。

連線狀況 、資料庫連結狀況 ﹐都必須在開店的時候就準備好 ﹐

必要的時候也可以測試一下連線情形 。

當確認工作完成 ﹐也就可以正式連線 ﹐準備讓客人點餐的作業

了 。

點擊 ”連線” 按鈕 ﹐連線成功的畫面 。

(圖 5-22)

連線失敗 。

Page 95

91

(圖 5-23)

如果連線失敗的話 ﹐就必須檢查 SERVER 端是否有開啟 ﹐或者

IP 、連接阜是否有誤 。

如在這些部分出了問題 ﹐可以在 SERVER 端更改 ﹐或者在 PDA

上直接輸入也是允許的 ﹐但是必須注意一點 ﹐不管是更改哪一邊的

IP 、連接阜 ﹐都必須將每一台機器都做確認 ﹐或者統一好 IP 位

址 ﹐再把程式做成安裝檔 ﹐重新安裝到 PDA 裡面 。

Page 96

92

(圖 5-24)

如果是資料庫沒有準備好的話 ﹐下方會出現資料庫無法載入 ﹐

就要檢查 PDA 內部的資料夾 ﹐當作成安裝檔的時候會產生一個

Shopping 的資料夾在檔案總管內( 當然因為安裝檔個人喜好有不同

的名字 ) ﹐在此資料夾內必須放入菜單的資料庫 ﹐而 PDA 讀取資料

庫的格式為 .cdb 檔 ﹐確認內部有一個 menu.cdb 檔 ﹐如果沒有就

必須將資料庫轉檔 ﹐轉檔的方式為 ﹕把 PDA 接在電腦上 ﹐必須要

有 ACTIVESYNC ﹐必須達到同步的效果 ﹐然後把檔案貼到新產生的

PDA 槽內 ﹐就會自動做轉換的動作了 ﹐如果要將 menu.cdb 檔拿出

PDA 槽 ﹐又會自動轉換成 mdb 檔 ﹐所以在這邊可以 ACTIVESYNC 的

選項內 ﹐選擇從 PDA 移出檔案時不轉換 ﹐就可以拿出 *.cdb 檔案

Page 97

93

了 。

(圖 5-25)

在 ACTIVESYNC 下的功能使用 ﹐可以選擇檔案如何轉換 ﹐請選擇檔

案轉換內的轉換設定鍵 。

Page 98

94

(圖 5-26)

進入之後選擇 Pocket Access 在點選編輯按鍵 。

(圖 5-27)

可選擇轉換或者不轉換 ﹐為了將*.cdb 檔案拿出,所以選擇不轉換 。

Page 99

95

(圖 5-28)

這樣就大功告成 ﹐而*.cdb 檔案就可以被 EVB 用做讀取的功用 。

接下來確定桌號後就可以進入選擇餐點 。

點擊 ’選擇餐點’

(圖 5-29)

Page 100

96

中間的部分為 DATAGRID ﹐是為了要將資料庫內的菜單秀出 。進入成

功後 ﹐點擊 ’請選擇餐點分類’的右方 box ﹐出現選項 。

(圖 5-30)

有三種餐點可以選擇 ﹐套餐 、湯類 、點心 。

選擇套餐 。

Page 101

97

(圖 5-31)

選擇了套餐之後 ﹐下方的 DATAGRID 就把資料庫內的套餐內容顯示出

來 。

點選湯類 、點心 ﹐也有一樣的效果 。

在這個畫面下可以點擊餐點下的選項 ﹐而點擊價格下面的表單 ﹐也

有跟點擊餐點下面選項相同的功能 ﹐都是點選一次欄位中的菜色 。

可以點選菜色的數量上限為 60 道菜色 。

超過數量上限的錯誤。

Page 102

98

(圖 5-32)

開始點餐 。

Page 103

99

(圖 5-33)

點選成功之後會在下方出現加入餐點為什麼菜色 ﹐在點選餐點之

後 ﹐可以到點餐清單確認 。

(圖 5-34)

確認無誤 ﹐點選成功 。

可以在右方的 CHECK BOX 點擊 ﹐來清查所點選的菜色 、數量是否正

確 。 此項功能可用可不用 ﹐目的是要標記住正確的餐點 ﹐方便做

點餐確認的工作 ﹐ 點選之後不會影響到任何作業 。

Page 104

100

(圖 5-35)

刪除某一樣錯誤的菜色,點擊兩次餐點名稱 。 例如 ﹕牛排 。

Page 105

101

(圖 5-36)

選擇 OK ﹐刪除牛排 。 選擇 Cancel 則不會更改點餐清單 。

(圖 5-37)

牛排成功刪除 。

如果全部都要重新點過 ﹐則使用清除清單 。

Page 106

102

(圖 5-38)

清除清單後 ﹐將會把點餐清單內的資料初始化 。

返回選擇餐點 ﹐繼續點餐 。 當點選菜色超過 8 樣 ﹐則使用↑↓鍵

來檢查其他頁數的菜色 。

Page 107

103

(圖 5-39)

下一頁

Page 108

104

(圖 5-40)

在上下頁的時候 ﹐所有功能都可以使用 ﹐例如 ﹕CHECK BOX 、刪

除 等功能都能夠運作 。

選擇傳送菜單 ﹐則會將所選擇的菜單送到櫃檯的 SERVER 端 。

當連線有問題 ﹐沒辦法傳送時會出現的錯誤 。

(圖 5-41)

傳送成功的時候 ﹐下方會出現傳送成功的字樣 。

Page 109

105

(圖 5-42)

而在 SERVER 端就會出現接受到菜單的訊息 。

Page 110

106

第六章、 結論與心得

涂允勝:

PDA 程式撰寫心得感想與困難突破 ﹕

時下 PDA 的應用範圍十分廣泛 ﹐所以在撰寫程式的時候就有滿

多種的語言可以選擇 ﹐而我們這一組選擇使用 VB 來編寫 PDA 的點餐

系統 ﹐所以我們整個架構都是由 VB 語言組成的 ﹐再使用 VB 語言寫

程式的時候可以感覺到 VB 是一個滿友善的語言 ﹐不僅僅是語法使用

方式或者是操作介面 ﹐可以非常快速的上手 。 雖然 VB 是一個簡易

上手的語言 ﹐但是它的功能也是非常強大的 。

在撰寫方面首先遇到的麻煩就屬於整個系統的架構了 ﹐因為是

PDA 點餐 ﹐勢必使用 PDA 的是服務生或者是顧客 ﹐所以在這方面的

考量 ﹐要把介面設計成便利性最高 ﹐卻又不落於簡陋 ﹐所以菜單

方面的呈現 ﹐則使用 Grid 的方式表現 ﹐使得系統能夠直接讀取出

菜單的種類 、樣式 ﹐不過在這邊 PDA 讀取的資料庫模式是讀取 .cdb

檔 ﹐而我們製作的資料庫則是 .mdb 檔 ﹐在這邊就必須要有轉檔的

動作 ﹐不然 PDA 將無法讀取出正確的資料庫內容 。

之後則是秀出菜單之後的點選方式 ﹐採用的是單擊一次 ”菜

名” 或者是 “價格” ﹐單擊之後會將資料存入陣列中 ﹐之後在秀

在點菜清單區 ﹐而點菜的最多數量為 60 ﹐超過則會顯示出 "選購

Page 111

107

商品超過 60 項!" ﹐然後沒有辦法在繼續增加餐點 ﹐單擊一次為點

一樣菜色 。

接下來是點菜清單區 ﹐在點菜清單區可以清楚的看出共點了幾

道菜 、多少錢 ﹐一個 Page 顯示 8 道菜 ﹐有 Page-Up 跟 Page-Down

可以提供換頁 ﹐還有 Check Box 來做為標記確認的餐點 ﹐純粹幫

助餐點的計量 、確認等工作 ﹐如果想要刪除某一道菜色 ﹐則在菜

名上連擊兩次 ﹐則會出現刪除訊息 ﹐在選擇是否要刪除 ﹐刪除之

後的欄位由下往上遞補 。 然後就是傳輸了 ﹐在傳輸的方面 ﹐點下

傳送菜單的按鈕 ﹐就會將點菜清單全部的資料送往 SERVER 端 ﹐然

後 PDA 上的資料則會清除 ﹐而桌號的更改則在連線區有桌號的 TEXT

BOX ﹐在欄位內填入正確的桌號即可 。 如果要清除掉所有的菜單 ﹐

則按下清除菜單的按鈕則會將所有點菜的資料刪除 ﹐並不會送到

SERVER 端 。

遭遇到的困難大多屬於對 VB 這語言的不熟悉 ﹐沒有辦法靈活運

用各項功能 ﹐以至於在程式上會有些許拖泥帶水的感覺 ﹐如果能夠

更熟悉各功能的細節 、以及使用方式 ﹐會使得在程式表現上有更俐

落的感覺 。

Page 112

108

黃振註:

最近兩三年來,無線網路環境越來越發達,硬體成本的降低,軟

體的支援性越來越高。如何有效的利用硬體設備,來降低成本支出,

也是本組所要研究的問題。

PDA 雖然耳熟能詳,但是功能為何,直到真正接觸之前,還是一

頭霧水,在設計此 PDA 點餐系統之前,甚至於不知道 PDA 還有分 OS

系統與 Win CE 系統。而之所以會選擇編寫 Win CE 系統的 PDA 點餐系

統,因 Win CE 的功能性較強,尤其是與 PC 做資料連結、資料傳輸的

部分,比較適用於現在的 Windows 系統。而 PDA 硬體的架設,也是逐

步測試,由 PDA 與 PC 的同步開始,一步一步的設定。

由於程式設計之初,就決定要利用無線網路的傳輸,所以在無線

網路環境的架設,是我們一開始遇到的問題,何謂無線網路,無線網

路的 protocal 要怎麼設定,無線網路卡的安裝、測試。市面上有關

此的書籍寫的不怎麼齊全,雖然在概念上都具備了,不過在於硬體實

際架設,真正使用的硬體,與書上的又不盡相同。只好從網路上尋找

資料,畢竟新的資訊,新的產品,最快取得相關資料的方式,就是藉

由網路。而 PDAorDIE 此網站,不僅是一個介紹網站,裡面的討論區

對我而言,如獲至寶一般的,可以藉由使用者的討論、分享。來找到

我要的解答。

Page 113

109

而 Server 端的程式,在於 Database 的讀取,與 PDA 資料的接收,

如何正確的表現出來,Server 與 Client 的連結方式,要如何設定、

編寫,是此部分所遭遇最大的問題。

另外在無線網路測試的部分,在 Winsock 建立之後,單機測試都

可以運作,但是運用到無線網路的時候,卻無法連線,問題在於無線

網路卡的安裝,安裝 PC 的無線網卡時,PC 原本的網卡卻掛掉了,推

測可能是硬體相衝所導致。另外 PDA 的無線網卡,甚至連驅動程式都

沒有,安裝完驅動程式之後,PDA 的網路設定又測試了幾天,最後,

終於可以正常連線。

Page 114

110

黃川泰:

其實vip資料的 Database是很簡單的,因為他沒有牽涉到其他

的表單,所以只要對此表單做資料處理就好了,不需要做其他的資料

連結,所有的資料控自都是非常的簡單而起單一的,不需要考慮太多,

但是如果遇到表單的控制與其他的表單有關聯時怎麼辦,情況會變的

非常複雜.

我們可以看看 menu 的修改的程式碼對您顯示出來程式的撰寫方

面會變的多複雜.

相信各位都有看到我的程式碼,為啥一個資料的新增刪除及修改

要做那麼多動作?必須要做那麼多的資料連結?

因為 Database的修改困難程度在於Database的各表單的關係是

否複雜,我們就以菜單來說吧!我們可以從 E-R 圖來看,一個菜單的修

改會影響到他的 3 個子表單及一個相關表單,所以單就資料流的搜尋

及指定是很簡單的,但是如果要修改一個大的相關表單是需要比較大

的功夫的

我們可以拿 ADD 這個指令來看,只是一個很簡單的資料增加的指

令,但是為啥要作那麼多的動作,因為一筆菜的增加必須要有相關素

材的連結以及其他種類菜單資料的連結(PDA 的轉檔方便所以必須多

分菜單種類出來)所以變的很複雜.

Page 115

111

我們可以拿 vip 修改的程式來看,他不需要做任何其他的資料連

結,因為他只需要做他本身的資料修改就可以完成他所有的動作,相

對於菜單的修改就不一樣因為他有許多的相關資料跟他有關,所以一

但改了一筆資料就必須改其他許多地方.

Page 116

112

參考資料

[1]David M. Kroenke, DataBase Processing, Fundamentals, Design

and Implementation, 8/e, Prentice-Hall, Inc. Programming, May

2002

[2]江意華,Visual Basic 6 完美經典,金禾資訊,Basic,April 2002

[3]鍾俊仁編譯,許建志校閱, Guy Eddon, Henry Eddon 原著,

Microsoft Visual Basic 6.0 元件程式設計開發指南(Programming

Components with Microsoft Visual Basic 6.0), Basic,華彩軟體,

Nov. 1999

[4]李永隆, 深入 PDA 程式設計 無線網路、硬體控制、主從式資料庫,

電腦程式語言,文魁資訊股份有限公司,April 2002

[5]賴禹丞,Pocket PC 專案開發範本,Basic(電腦程式語言),作業系

統,金禾資訊, April 2002

[6]季延平譯, Gary B. Shelly, Thomas J. Cashman, Haryy J.

Rosenblatt 著,系統分析與設計(System Analysis and design),系

統分析,東華書局,Nov. 1999

網站

小雄資訊服務中心 http://infoserv.com.tw/

Page 117

113

PDAorDie http://www.pdaordie.com/

Google http://www.google.com/intl/zh-TW/

專題報告 http://www.cdic.gov.tw/research4.jsp

Page 118

114

附錄

A.Server 端

經理介面

Form load 程式碼:

Private Sub form_load()

Set cn = New ADODB.Connection

Set rs = New ADODB.Recordset

Dim mysqq As String

mysqq = "select 菜單.菜名 ,菜單.Kind ,菜單.價格 From 菜單

order by 菜單.Kind asc"

cn.ConnectionString = "dsn=db1"//取得資料集

cn.CursorLocation = adUseClient

cn.Open

rs.Open mysqq, cn, adOpenStatic, adLockOptimistic

Set DataGrid1.DataSource = rs //datagrid1 取得資料集

Text5.Text = rs!kind

Text1.Text = rs!菜名

Text2.Text = rs!價格

End Sub

Page 119

115

搜尋程式碼:

Private Sub cmdfind_click()

Dim i As Integer

Dim ret As Integer

If textfind1.Text = "" Then

Beep

rmsg = MsgBox("", vbOKOnly, "°TR§")

Exit Sub

End If

rs.MoveFirst

rs.Find "請輸入搜尋值='" & textfind1.Text & "訊息'"

If rs.EOF = True Then

rs.MoveFirst

Beep

rmsg = MsgBox("沒此菜", vbOKOnly, "訊息")

Exit Sub

End If

Page 120

116

i = rs! »

textfind2.Text = i

Text6.Text = rs!kind

Text5.Text = rs!kind

Text1.Text = rs!菜名

Text2.Text = rs!價格

End Sub

第一筆程式碼:

Private Sub cmdFirst_Click()

rs.MoveFirst

Text5.Text = rs!kind

Text1.Text = rs!菜名

Text2.Text = rs!價格

End Sub

最後一筆程式碼:

Page 121

117

Private Sub cmdLast_Click()

rs.MoveLast

Text5.Text = rs!kind

Text1.Text = rs!菜名

Text2.Text = rs!價格

End Sub

下一筆程式碼:

Private Sub cmdNext_Click()

rs.MoveNext

If rs.EOF = True Then

rs.MoveLast

End If

Text5.Text = rs!kind

Text1.Text = rs!菜名

Text2.Text = rs!價格

End Sub

Page 122

118

上一筆程式碼:

Private Sub cmdup_Click()

rs.MovePrevious

If rs.BOF = True Then

rs.MoveFirst

End If

Text5.Text = rs!kind

Text1.Text = rs!菜名

Text2.Text = rs!價格

End Sub

新增程式碼:

Private Sub cmdAdd_Click()

Dim kind As String

Dim num As Integer

Dim rmsg As String

Page 123

119

If textadd1.Text = "" Or textadd2.Text = "" Or textkind1.Text

= "" Or Text3.Text = "" Or Text4.Text = "" Then

Beep

rmsg = MsgBox("請輸入要菜名及價格", vbOKOnly, "訊息")

Exit Sub

End If //確定新增菜的資料都有輸入

Set cc3 = New ADODB.Connection //以下皆為打開菜單種類資料流

的動作

Set rr3 = New ADODB.Recordset

cc3.ConnectionString = "dsn=db1"

cc3.Open

rr3.Open "菜單種類", cc3, adOpenStatic, adLockOptimistic

rr3.AddNew

//新增菜單在菜單種類上

rr3!菜名 = textadd1.Text

rr3!kind = textkind1.Text

rr3.Update

kind = textkind1.Text

rs.AddNew //新增菜單在菜單上

rs!菜名 = textadd1.Text

Page 124

120

rs!kind = textkind1.Text

rs!價格 = CLng(textadd2.Text)

Set cn0 = New ADODB.Connection

Set rs0 = New ADODB.Recordset

cn0.ConnectionString = "dsn=db1"

cn0.Open

rs0.Open "所須材料", cn0, adOpenStatic, adLockOptimistic//

以下皆為打開所需材料資料流的動作

rs0.AddNew //新增所需材料

rs0!菜名 = textadd1.Text

rs0!材料 = Text3.Text

rs0.AddNew

rs0!菜名 = textadd1.Text

rs0!材料 = Text4.Text

rs0.Update

Set cn1 = New ADODB.Connection //以下皆為打開各個種類菜單

的資料流動作

Set rs1 = New ADODB.Recordset

cn1.ConnectionString = "dsn=db1"

Page 125

121

cn1.Open

rs1.Open kind, cn1, adOpenStatic, adLockOptimistic

rs1.MoveLast

num = rs1!編碼

num = num + 1

rs1.AddNew //新增菜在各類菜單

rs1!編碼 = num

rs1!菜名 = textadd1.Text

rs1!價格 = textadd2.Text

rs1.Update

Set cc2 = New ADODB.Connection

Set rr2 = New ADODB.Recordset

cc2.ConnectionString = "dsn=db1"

cc2.Open

rr2.Open "訂單", cc2, adOpenStatic, adLockOptimistic

rr2.AddNew

rr2!菜名 = textadd1.Text

rr2!數量 = "0"

rr2.Update

Page 126

122

End Sub

刪除程式碼

Private Sub cmdDelete_Click()

Dim ret As Integer

Dim m_name As String

Dim k_name As String

Set cc3 = New ADODB.Connection

Set rr3 = New ADODB.Recordset

cc3.ConnectionString = "dsn=db1"

cc3.Open

rr3.Open "菜單種類", cc3, adOpenStatic, adLockOptimistic//

打開”菜單種類”資料流

Set cn0 = New ADODB.Connection

Set rs0 = New ADODB.Recordset

cn0.ConnectionString = "dsn=db1"

cn0.Open

rs0.Open "所須材料", cn0, adOpenStatic, adLockOptimistic//

打開”所須材料”的資料流

Set cc2 = New ADODB.Connection

Page 127

123

Set rr2 = New ADODB.Recordset

cc2.ConnectionString = "dsn=db1"

cc2.Open

rr2.Open "訂單", cc2, adOpenStatic, adLockOptimistic//打開”

訂單”的資料流

Beep

ret = MsgBox("是否真的要刪除此菜:[" & rs!菜名 & "]", vbYesNo,

"確認刪除")

If ret = vbYes Then

m_name = rs!菜名

k_name = rs!kind

rs0.Find "菜名='" & rs!菜名 & "'"//下三行是做各資料流的搜

尋動作

rr3.Find "菜名='" & rs!菜名 & "'"

rr2.Find "菜名='" & rs!菜名 & "'"

rr2.Delete //以下為訂單及所需材料及菜單種類及菜單的刪除

If rr2.EOF = True Then

rr2.MoveLast

End If

Page 128

124

rr3.Delete

If rr3.EOF = True Then

rr3.MoveLast

End If

rs0.Delete

rs0.MoveNext

rs0.Delete

If rs0.EOF = True Then

rs0.MoveLast

End If

rs.Delete

rs.MoveNext

If rs.EOF = True Then

rs.MoveLast

End If

Set cc = New ADODB.Connection //以下是取得各套餐的資料

Page 129

125

流及刪除動作

Set rr = New ADODB.Recordset

cc.ConnectionString = "dsn=db1"

cc.Open

rr.Open k_name, cc, adOpenStatic, adLockOptimistic

rr.Find "菜名='" & m_name & "'"

rr.Delete

rr.MoveNext

If rr.EOF = True Then

rr.MoveLast

End If

Else

Exit Sub

End If

End Sub

修改程式碼:

Private Sub cmdEdit_Click()

Dim ret As Integer

Dim menu As String

Page 130

126

Dim kind1 As String

kind1 = Text5.Text

Set cn1 = New ADODB.Connection///打開各種菜單的資料流

Set rs1 = New ADODB.Recordset

cn1.ConnectionString = "dsn=db1"

cn1.Open

rs1.Open kind1, cn1, adOpenStatic, adLockOptimistic

Set cc3 = New ADODB.Connection //打開菜單種類的資料流

Set rr3 = New ADODB.Recordset

cc3.ConnectionString = "dsn=db1"

cc3.Open

rr3.Open "菜單種類", cc3, adOpenStatic, adLockOptimistic

Set cn0 = New ADODB.Connection ///打開所需材料的資料流

Set rs0 = New ADODB.Recordset

cn0.ConnectionString = "dsn=db1"

Page 131

127

cn0.Open

rs0.Open "所須材料", cn0, adOpenStatic, adLockOptimistic

m_name = rs!菜名

If Text5.Text = rs!kind Then

If Text1.Text = "" Or Text2.Text = "" Then

Beep

ret = MsgBox("請輸入要改的菜名及價格", vbOKOnly, "訊

息")

Exit Sub

End If

rs1.MoveFirst

rs0.MoveFirst

rr3.MoveFirst

rs0.Find "菜名='" & rs!菜名 & "'" //下三行是做各資料流

的搜尋動作

Page 132

128

rs1.Find "菜名='" & rs!菜名 & "'"

rr3.Find "菜名='" & rs!菜名 & "'"

rs.Update "菜名", Text1.Text //以下是各資料流的修改

動作

rs.Update "價格", CLng(Text2.Text)

rs.Update "Kind", Text5.Text

rs1.Update "菜名", Text1.Text

rs1.Update "價格", CLng(Text2.Text)

rs0.Update "菜名", Text1.Text

rs0.MoveNext

rs0.Update "菜名", Text1.Text

rr3.Update "菜名", Text1.Text

rr3.Update "Kind", Text5.Text

Else

Beep

Page 133

129

ret = MsgBox("請確定種類", vbOKOnly, "訊息")

Exit Sub

End If

End Sub

櫃臺介面

Form load 程式碼:

Login 程式碼:

Private cn2 As ADODB.Connection

Private rs2 As ADODB.Recordset

Private Sub Form_load()

Set cn2 = New ADODB.Connection

Set rs2 = New ADODB.Recordset

cn2.ConnectionString = "dsn=db1"

cn2.CursorLocation = adUseClient

cn2.Open

Page 134

130

rs2.Open "pass", cn2, adOpenStatic, adLockOptimistic

End Sub

Private Sub cmdOK_Click()

Dim ps As String

ps = Text2.Text

rs2.MoveFirst

rs2.Find "User='" & Text1.Text & "'"

If rs2.EOF = True Then

Beep

ret = MsgBox("sorry no this user")

rs2.MoveFirst

End

End If

If rs2!Password = ps Then

Unload Me

Form7.Show

Else

Beep

ret = MsgBox("password error")

Page 135

131

End If

End Sub

Private Sub cmdch_click()

End

End Sub

Kitchen 程式碼:

Private cn3 As ADODB.Connection

Private rs3 As ADODB.Recordset

Private cn5 As ADODB.Connection

Private rs5 As ADODB.Recordset

Private Sub cmdEdit_Click()

Form5.Show

End Sub

Private Sub cmdseeinfo_Click()

Forminfo.Show

End Sub

Page 136

132

Private Sub cmdseesin_Click()

訂單.Show

End Sub

Private Sub cmdtest_Click()

Dim strData As String

Dim m_name As String

Dim tmp As String

Dim i As Integer

m_nane = ""

Dim nFn1 As Long

nFn1 = FreeFile

Open cppPath & "test.txt" For Input As #nFn1

While Not EOF(nFn1)

Input #nFn1, tmp

If tmp = "2" Then

Text2.Text = "interp"

Exit Sub

Page 137

133

End If

Text1.Text = Text1.Text & tmp

Wend

End Sub

Private Sub Form_load()

Dim mysqo As String

mysqo = "select 菜單.菜名 ,菜單.Kind ,菜單.價格 From 菜單

order by 菜單.Kind asc"

Winsock1.Protocol = sckTCPProtocol

Winsock1.LocalPort = 7577

Winsock1.Listen

Set cn3 = New ADODB.Connection

Set rs3 = New ADODB.Recordset

cn3.ConnectionString = "dsn=db1"

cn3.Open

rs3.Open mysqo, cn3, adOpenStatic, adLockOptimistic

Set DataGrid1.DataSource = rs3

End Sub

Page 138

134

Private Sub cmdref_Click()

Dim mysqo As String

mysqo = "select 菜單.菜名 ,菜單.Kind ,菜單.價格 From 菜單

order by 菜單.Kind asc"

Set cn3 = New ADODB.Connection

Set rs3 = New ADODB.Recordset

cn3.ConnectionString = "dsn=db1"

cn3.Open

rs3.Open mysqo, cn3, adOpenStatic, adLockOptimistic

Set DataGrid1.DataSource = rs3

End Sub

Private Sub cmdor_click()

Dim cn1 As ADODB.Connection

Dim rs1 As ADODB.Recordset

Dim criteria As String

Dim int1 As Integer

Set cn1 = New ADODB.Connection

Set rs1 = New ADODB.Recordset

int1 = 0

Page 139

135

cn1.ConnectionString = "dsn=db1"

cn1.Open

rs1.Open "訂單", cn1, adOpenStatic, adLockOptimistic

criteria = rs3!菜名

rs1.Find "菜名='" & criteria & "'"

int1 = rs1!數量

int1 = int1 + 1

rs1!數量 = int1

rs1.Update

rs1.MoveFirst

End Sub

Private Sub Winsock1_ConnectionRequest(ByVal requestID As

Long)

Winsock1.Close

Winsock1.Accept requestID

End Sub

Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)

Dim strData As String

Winsock1.GetData strData

Page 140

136

test.Text = test.Text + strData

End Sub

所需材料程式碼:

Private cn4 As ADODB.Connection

Private rs4 As ADODB.Recordset

Private rs5 As ADODB.Recordset

Private cn5 As ADODB.Connection

Private Sub cmdneed_Click()

Dim mysql As String

mysql = "select 所需材料.菜名,素材.素材名,素材.種類 From 所

需材料,素材 where 所需材料.材料 = 素材.素材名 order by 所需

材料.菜名 desc"

Set cn4 = New ADODB.Connection

Set rs4 = New ADODB.Recordset

cn4.ConnectionString = "dsn=db1"

cn4.Open

rs4.Open mysql, cn4, adOpenStatic

Page 141

137

Set DataGrid1.DataSource = rs4

End Sub

Private Sub Form_load()

Dim mysql As String

mysql = "select 所需材料.菜名,素材.素材名,素材.種類 From 所

需材料,素材 where 所需材料.材料 = 素材.素材名 order by 所需

材料.菜名 desc"

Set cn4 = New ADODB.Connection

Set rs4 = New ADODB.Recordset

cn4.ConnectionString = "dsn=db1"

cn4.Open

rs4.Open mysql, cn4, adOpenStatic

Set DataGrid1.DataSource = rs4

End Sub

Private Sub cmd_click()

Set cn5 = New ADODB.Connection

Set rs5 = New ADODB.Recordset

Page 142

138

cn5.ConnectionString = "dsn=db1"

cn5.Open

rs5.Open "素材", cn5, adOpenStatic

Set DataGrid1.DataSource = rs5

End Sub

Private Sub cmdone_click()

Dim mysql As String

Dim i_name As String

i_name = Text1.Text

mysql = "select 所需材料.菜名,素材.素材名,素材.種類 From 所

需材料,素材 where 所需材料.材料 = 素材.素材名 order by 所需

材料.菜名 desc"

Set cn5 = New ADODB.Connection

Set rs5 = New ADODB.Recordset

cn5.ConnectionString = "dsn=db1"

cn5.Open

rs5.Open mysql, cn5, adOpenStatic

Set DataGrid1.DataSource = rs5

Page 143

139

End Sub

VIP

Form load 程式碼:

Private Sub form_load()

Set cnvip = New ADODB.Connection

Set rsvip = New ADODB.Recordset

Dim myvip As String

myvip = "select vip.姓名 ,vip.電話 From vip order by vip.

姓名 asc"

cnvip.ConnectionString = "dsn=db1"

cnvip.CursorLocation = adUseClient

cnvip.Open

rsvip.Open myvip, cnvip, adOpenStatic, adLockOptimistic

Set DataGrid1.DataSource = rsvip

Text4.Text = rsvip!姓名

Page 144

140

Text5.Text = rsvip!電話

End Sub

新增程式碼

Private Sub cmdAdd_Click()

If Text1.Text = "" Or Text2.Text = "" Then

Beep

rmsg = MsgBox("請輸入資料", vbOKOnly, "訊息")

Exit Sub

End If

rsvip.AddNew

rsvip!姓名 = Text1.Text

rsvip!電話 = Text2.Text

rsvip.Update

End Sub

Page 145

141

刪除程式碼

Private Sub cmddel_Click()

Dim ret As String

Beep

ret = MsgBox("是否真的要刪除此顧客:[" & rsvip!姓名 & "]",

vbYesNo, "確認刪除")

If ret = vbYes Then

rsvip.Delete

rsvip.MoveNext

If rsvip.EOF = True Then

rsvip.MoveLast

End If

Else

Exit Sub

End If

End Sub

Page 146

142

修改程式碼

Private Sub cmdEdit_Click()

If Text4.Text = "" Or Text5.Text = "" Then

Beep

rmsg = MsgBox("請輸入要改的資料", vbOKOnly, "訊息")

Exit Sub

End If

rsvip.Update "姓名", Text4.Text

rsvip.Update "電話", Text5.Text

End Sub

第一筆程式碼

Private Sub cmdFirst_Click()

rsvip.MoveFirst

Text4.Text = rsvip!姓名

Page 147

143

Text5.Text = rsvip!電話

End Sub

最後一筆程式碼

Private Sub cmdLast_Click()

rsvip.MoveLast

Text4.Text = rsvip!姓名

Text5.Text = rsvip!電話

End Sub

下一筆程式碼

Private Sub cmdNext_Click()

rsvip.MoveNext

If rsvip.EOF = True Then

rsvip.MoveLast

End If

Text4.Text = rsvip!姓名

Page 148

144

Text5.Text = rsvip!電話

End Sub

上一筆程式碼

Private Sub cmdup_Click()

rsvip.MovePrevious

If rsvip.BOF = True Then

rsvip.MoveFirst

End If

Text4.Text = rsvip!姓名

Text5.Text = rsvip!電話

End Sub

Page 149

145

B.Client 端

程式碼的解說 ﹕

Option Explicit

Dim conn As ADOCE.Connection

Dim DBFilename As String

Dim strTableName As String

Dim intTotalItem As Integer

Dim strItemName(60) As String

Dim intItemMoney(60) As Integer

Dim intItemCheck(60) As Integer

Dim intCurrentItemNo As Integer

Dim intCurrentPageNo As Integer

Dim intTotalPageNo As Integer

Page 150

146

Dim lngTotalMoney As Long

Dim intTotalSelectItemNo As Integer

Dim strShopFilename As String

Dim FoodCounter As Integer

/*將所需要的變數做定義

Private Sub cmdClose_Click()

WinSock1.Close

WinSock1.LocalPort = 0

cmdConnect.Enabled = True

cmdClose.Enabled = False

lblStatus.Caption = "連線關閉"

End Sub

/*離線按鈕的設定

Page 151

147

Private Sub cmdConnect_Click()

WinSock1.Protocol = sckTCPProtocol

WinSock1.RemoteHost = txtRemortHost.Text

WinSock1.RemotePort = txtRemortPort.Text

WinSock1.Connect

lblStatus.Caption = "連線開啟"

If WinSock1.State sckConnected Then

lblStatus.Caption = CStr(GetState(WinSock1))

Exit Sub

End If

cmdConnect.Enabled = False

cmdClose.Enabled = True

Page 152

148

End Sub

/*連線按鈕點擊之後產生的後續動作

Private Sub cmdTrains_Click()

If WinSock1.State sckConnected Then

lblStatus.Caption = CStr(GetState(WinSock1))

Exit Sub

End If

FoodCounter = 0

Dim strOrder As String

Dim i As Integer

For i = 1 To 60 Step 1

If intItemMoney(i) = "" Then Exit For

FoodCounter = FoodCounter + 1

strOrder = CStr(FoodCounter) + " : " + txtTable.Text + " : "

+ strItemName(i) + " : " + intItemMoney(i) + " 元 " + vbCrLf

WinSock1.SendData strOrder

Page 153

149

Next i

Dim intTemp As Integer

For intTemp = 1 To 60

strItemName(intTemp) = ""

intItemMoney(intTemp) = ""

intItemCheck(intTemp) = 0

Next

intTotalItem = 0

intCurrentItemNo = 1

intCurrentPageNo = 1

lngTotalMoney = 0

intTotalSelectItemNo = 0

lblBuyCount.Caption = intTotalSelectItemNo

lblShopMoney.Caption = lngTotalMoney

lblTotalCount.Caption = intTotalItem

Page 154

150

lblMessage.Caption = "傳送成功"

ClearList

DisplayListData intCurrentPageNo

End Sub

/*傳送菜單按鈕 ﹐傳什麼資料以及成功之後的後續動作 。

Private Sub Form_Load()

Dim strPath As String

strPath = App.Path

If strPath = "\" Then

strPath = ""

End If

DBFilename = strPath & "\menu.cdb"

Page 155

151

If DBExists(DBFilename) = True Then

lblMessage.Caption = "資料庫準備就緒"

Else

lblMessage.Caption = "資料庫無法載入"

End If

fraItems.Left = TabStrip1.ClientLeft

fraItems.Top = TabStrip1.ClientTop

fraItems.Width = TabStrip1.ClientWidth

fraItems.Height = TabStrip1.ClientHeight

fraList.Left = TabStrip1.ClientLeft

fraList.Top = TabStrip1.ClientTop

fraList.Width = TabStrip1.ClientWidth

fraList.Height = TabStrip1.ClientHeight

fraList.Visible = False

fraItems.Visible = True

Page 156

152

intTotalItem = 0

intCurrentItemNo = 1

intCurrentPageNo = 1

lngTotalMoney = 0

intTotalSelectItemNo = 0

Dim intTemp As Integer

For intTemp = 1 To 60

intItemCheck(intTemp) = 0

Next

End Sub

/*測試資料庫是否存在 。

Page 157

153

Private Sub cboItems_Click()

Dim strTemp As String

strTemp = cboItems.Text

Select Case strTemp

Case "套餐"

strTableName = "套餐"

Case "湯類"

strTableName = "湯類"

Case "點心"

strTableName = "點心"

End Select

If strTableName "" Then

DisplayDataToGrid strTableName

Page 158

154

End If

grdItems.Row = 0

End Sub

/*選擇餐點的種類 ﹐以及選了之後 CALL 另外的 SUB 來秀 。

Private Sub TabStrip1_Click()

Select Case TabStrip1.SelectedItem.Key

Case "SelectItems" '選擇餐點

fraList.Visible = False

fraItems.Visible = True

fraConnect.Visible = False

Case "ListItems" '點餐清單

fraList.Visible = True

fraItems.Visible = False

fraConnect.Visible = False

DisplayListData (intCurrentPageNo)

Page 159

155

Case "Connect" '連線區

fraList.Visible = False

fraItems.Visible = False

fraConnect.Visible = True

End Select

End Sub

/*TABSTRIP 的功能設定 ﹐三個選項 。

Private Function DBExists(strFileName As String) As Boolean

If FileSystem1.Dir(strFileName) "" Then

DBExists = True

Else

DBExists = False

End If

End Function

Page 160

156

Function connOpen() As Boolean

On Error Resume Next

connOpen = True

If conn Is Nothing Then

Set conn = CreateObject("ADOCE.Connection.3.0")

conn.Open DBFilename

If conn.Errors.Count > 0 Then

MsgBox "errors in connOpen", vbOKOnly

connOpen = False

End If

End If

On Error GoTo 0

End Function

Sub connClose()

On Error Resume Next

conn.Close

Set conn = Nothing

Page 161

157

On Error GoTo 0

End Sub

/*打開資料庫

Private Sub DisplayDataToGrid(tablename As String)

Dim rs As ADOCE.Recordset

Dim intCount As Integer

Dim strData As String

Dim intCol As Integer

Dim intCounter As Integer

grdItems.Rows = 0

grdItems.AddItem ("商品" + vbTab + "價格")

intCounter = 0

intCol = 0

For intCol = 0 To 1

grdItems.Col = intCol

grdItems.Row = intCounter

grdItems.CellFontSize = 10

Next

Page 162

158

If connOpen = True Then

Set rs = CreateObject("ADOCE.Recordset.3.0")

On Error Resume Next

rs.Open "select * from " & tablename, conn,

adOpenForwardOnly, adLockReadOnly

rs.MoveFirst

Do While Not rs.EOF

strData = CStr(rs(1).Value) + vbTab + CStr(rs(2).Value)

grdItems.AddItem strData

intCounter = intCounter + 1

For intCol = 0 To 1

grdItems.Col = intCol

grdItems.Row = intCounter

grdItems.CellFontSize = 10

Next

rs.MoveNext

Loop

rs.Close

Page 163

159

Set rs = Nothing

On Error GoTo 0

End If

grdItems.TopRow = 0

grdItems.ColWidth(0) = 2000

grdItems.ColWidth(1) = 1000

connClose

End Sub

Private Sub grdItems_Click()

If grdItems.RowSel = 0 Then

Exit Sub

ElseIf grdItems.Clip = "" Then

Exit Sub

End If

If intTotalItem > 60 Then

lblMessage.Caption = "選購商品超過 60 項!"

Page 164

160

Exit Sub

End If

intTotalItem = intTotalItem + 1

strItemName(intTotalItem) =

grdItems.TextMatrix(grdItems.RowSel, 0)

intItemMoney(intTotalItem) =

grdItems.TextMatrix(grdItems.RowSel, 1)

lngTotalMoney = lngTotalMoney +

grdItems.TextMatrix(grdItems.RowSel, 1)

lblShopMoney.Caption = lngTotalMoney

lblMessage.Caption = "加入商品:" &

grdItems.TextMatrix(grdItems.RowSel, 0)

lblTotalCount.Caption = intTotalItem

intTotalPageNo = ((lblTotalCount - 1) \ 8) + 1

End Sub

/*秀出表單的方式

Private Sub DisplayListData(PageNo As Integer)

Page 165

161

ItemName1.Caption = strItemName((PageNo - 1) * 8 + 1)

ItemName2.Caption = strItemName((PageNo - 1) * 8 + 2)

ItemName3.Caption = strItemName((PageNo - 1) * 8 + 3)

ItemName4.Caption = strItemName((PageNo - 1) * 8 + 4)

ItemName5.Caption = strItemName((PageNo - 1) * 8 + 5)

ItemName6.Caption = strItemName((PageNo - 1) * 8 + 6)

ItemName7.Caption = strItemName((PageNo - 1) * 8 + 7)

ItemName8.Caption = strItemName((PageNo - 1) * 8 + 8)

ItemMoney1.Caption = intItemMoney((PageNo - 1) * 8 + 1)

ItemMoney2.Caption = intItemMoney((PageNo - 1) * 8 + 2)

ItemMoney3.Caption = intItemMoney((PageNo - 1) * 8 + 3)

ItemMoney4.Caption = intItemMoney((PageNo - 1) * 8 + 4)

ItemMoney5.Caption = intItemMoney((PageNo - 1) * 8 + 5)

ItemMoney6.Caption = intItemMoney((PageNo - 1) * 8 + 6)

ItemMoney7.Caption = intItemMoney((PageNo - 1) * 8 + 7)

ItemMoney8.Caption = intItemMoney((PageNo - 1) * 8 + 8)

ItemCheck1.Value = intItemCheck((PageNo - 1) * 8 + 1)

Page 166

162

ItemCheck2.Value = intItemCheck((PageNo - 1) * 8 + 2)

ItemCheck3.Value = intItemCheck((PageNo - 1) * 8 + 3)

ItemCheck4.Value = intItemCheck((PageNo - 1) * 8 + 4)

ItemCheck5.Value = intItemCheck((PageNo - 1) * 8 + 5)

ItemCheck6.Value = intItemCheck((PageNo - 1) * 8 + 6)

ItemCheck7.Value = intItemCheck((PageNo - 1) * 8 + 7)

ItemCheck8.Value = intItemCheck((PageNo - 1) * 8 + 8)

End Sub

/*秀出點菜清單的方式

Private Sub cmdPageDown_Click()

Dim temp

temp = intTotalSelectItemNo

If intCurrentPageNo >= intTotalPageNo Then

Exit Sub

End If

intCurrentPageNo = intCurrentPageNo + 1

Page 167

163

DisplayListData intCurrentPageNo

intTotalSelectItemNo = temp

lblBuyCount.Caption = intTotalSelectItemNo

End Sub

/*下一頁的表現方式

Private Sub cmdPageUp_Click()

Dim temp

temp = intTotalSelectItemNo

If intCurrentPageNo = 1 Then

Exit Sub

End If

intCurrentPageNo = intCurrentPageNo - 1

DisplayListData intCurrentPageNo

intTotalSelectItemNo = temp

lblBuyCount.Caption = intTotalSelectItemNo

End Sub

Page 168

164

/*上一頁的表現方式

Private Sub ClearList()

ItemName1.Caption = ""

ItemName2.Caption = ""

ItemName3.Caption = ""

ItemName4.Caption = ""

ItemName5.Caption = ""

ItemName6.Caption = ""

ItemName7.Caption = ""

ItemName8.Caption = ""

ItemMoney1.Caption = ""

ItemMoney2.Caption = ""

ItemMoney3.Caption = ""

ItemMoney4.Caption = ""

ItemMoney5.Caption = ""

ItemMoney6.Caption = ""

ItemMoney7.Caption = ""

Page 169

165

ItemMoney8.Caption = ""

End Sub

/*清除畫面上的資料

Private Sub MoveDate(ItemNo As Integer)

Dim intTemp

For intTemp = ItemNo To intTotalItem

strItemName(intTemp) = strItemName(intTemp + 1)

intItemMoney(intTemp) = intItemMoney(intTemp + 1)

intItemCheck(intTemp) = intItemCheck(intTemp + 1)

Next

strItemName(intTotalItem + 1) = ""

intItemMoney(intTotalItem + 1) = ""

intItemCheck(intTotalItem + 1) = 0

ClearList

DisplayListData intCurrentPageNo

Page 170

166

End Sub

Private Sub ItemName1_DblClick()

Dim temp

temp = MsgBox("你確定要刪除:" & ItemName1.Caption, vbOKCancel,

"刪除商品")

If temp = vbCancel Then

Exit Sub

End If

intCurrentItemNo = (intCurrentPageNo - 1) * 8 + 1

intTotalItem = intTotalItem - 1

lblTotalCount.Caption = intTotalItem

lngTotalMoney = lngTotalMoney -

intItemMoney(intCurrentItemNo)

lblShopMoney.Caption = lngTotalMoney

If intItemCheck(intCurrentItemNo) = 1 Then

intTotalSelectItemNo = intTotalSelectItemNo - 1

Page 171

167

lblBuyCount.Caption = intTotalSelectItemNo

End If

MoveDate intCurrentItemNo

End Sub

/*刪除點菜資料的方式 ﹐第一欄位到第八欄位都是一樣 。

Private Sub ItemName2_DblClick()

Dim temp

temp = MsgBox("你確定要刪除:" & ItemName2.Caption, vbOKCancel,

"刪除商品")

If temp = vbCancel Then

Exit Sub

End If

intCurrentItemNo = (intCurrentPageNo - 1) * 8 + 2

intTotalItem = intTotalItem - 1

lblTotalCount.Caption = intTotalItem

Page 172

168

lngTotalMoney = lngTotalMoney -

intItemMoney(intCurrentItemNo)

lblShopMoney.Caption = lngTotalMoney

If intItemCheck(intCurrentItemNo) = 1 Then

intTotalSelectItemNo = intTotalSelectItemNo - 1

lblBuyCount.Caption = intTotalSelectItemNo

End If

MoveDate intCurrentItemNo

End Sub

Private Sub ItemName3_DblClick()

Dim temp

temp = MsgBox("你確定要刪除:" & ItemName3.Caption, vbOKCancel,

"刪除商品")

If temp = vbCancel Then

Exit Sub

Page 173

169

End If

intCurrentItemNo = (intCurrentPageNo - 1) * 8 + 3

intTotalItem = intTotalItem - 1

lblTotalCount.Caption = intTotalItem

lngTotalMoney = lngTotalMoney -

intItemMoney(intCurrentItemNo)

lblShopMoney.Caption = lngTotalMoney

If intItemCheck(intCurrentItemNo) = 1 Then

intTotalSelectItemNo = intTotalSelectItemNo - 1

lblBuyCount.Caption = intTotalSelectItemNo

End If

MoveDate intCurrentItemNo

End Sub

Page 174

170

Private Sub ItemName4_DblClick()

Dim temp

temp = MsgBox("你確定要刪除:" & ItemName4.Caption, vbOKCancel,

"刪除商品")

If temp = vbCancel Then

Exit Sub

End If

intCurrentItemNo = (intCurrentPageNo - 1) * 8 + 4

intTotalItem = intTotalItem - 1

lblTotalCount.Caption = intTotalItem

lngTotalMoney = lngTotalMoney -

intItemMoney(intCurrentItemNo)

lblShopMoney.Caption = lngTotalMoney

If intItemCheck(intCurrentItemNo) = 1 Then

intTotalSelectItemNo = intTotalSelectItemNo - 1

Page 175

171

lblBuyCount.Caption = intTotalSelectItemNo

End If

MoveDate intCurrentItemNo

End Sub

Private Sub ItemName5_DblClick()

Dim temp

temp = MsgBox("你確定要刪除:" & ItemName5.Caption, vbOKCancel,

"刪除商品")

If temp = vbCancel Then

Exit Sub

End If

intCurrentItemNo = (intCurrentPageNo - 1) * 8 + 5

intTotalItem = intTotalItem - 1

lblTotalCount.Caption = intTotalItem

lngTotalMoney = lngTotalMoney -

Page 176

172

intItemMoney(intCurrentItemNo)

lblShopMoney.Caption = lngTotalMoney

If intItemCheck(intCurrentItemNo) = 1 Then

intTotalSelectItemNo = intTotalSelectItemNo - 1

lblBuyCount.Caption = intTotalSelectItemNo

End If

MoveDate intCurrentItemNo

End Sub

Private Sub ItemName6_DblClick()

Dim temp

temp = MsgBox("你確定要刪除:" & ItemName6.Caption, vbOKCancel,

"刪除商品")

If temp = vbCancel Then

Exit Sub

End If

Page 177

173

intCurrentItemNo = (intCurrentPageNo - 1) * 8 + 6

intTotalItem = intTotalItem - 1

lblTotalCount.Caption = intTotalItem

lngTotalMoney = lngTotalMoney -

intItemMoney(intCurrentItemNo)

lblShopMoney.Caption = lngTotalMoney

If intItemCheck(intCurrentItemNo) = 1 Then

intTotalSelectItemNo = intTotalSelectItemNo - 1

lblBuyCount.Caption = intTotalSelectItemNo

End If

MoveDate intCurrentItemNo

End Sub

Private Sub ItemName7_DblClick()

Dim temp

temp = MsgBox("你確定要刪除:" & ItemName7.Caption, vbOKCancel,

"刪除商品")

Page 178

174

If temp = vbCancel Then

Exit Sub

End If

intCurrentItemNo = (intCurrentPageNo - 1) * 8 + 7

intTotalItem = intTotalItem - 1

lblTotalCount.Caption = intTotalItem

lngTotalMoney = lngTotalMoney -

intItemMoney(intCurrentItemNo)

lblShopMoney.Caption = lngTotalMoney

If intItemCheck(intCurrentItemNo) = 1 Then

intTotalSelectItemNo = intTotalSelectItemNo - 1

lblBuyCount.Caption = intTotalSelectItemNo

End If

MoveDate intCurrentItemNo

Page 179

175

End Sub

Private Sub ItemName8_DblClick()

Dim temp

temp = MsgBox("你確定要刪除:" & ItemName8.Caption, vbOKCancel,

"刪除商品")

If temp = vbCancel Then

Exit Sub

End If

intCurrentItemNo = (intCurrentPageNo - 1) * 8 + 8

intTotalItem = intTotalItem - 1

lblTotalCount.Caption = intTotalItem

lngTotalMoney = lngTotalMoney -

intItemMoney(intCurrentItemNo)

lblShopMoney.Caption = lngTotalMoney

If intItemCheck(intCurrentItemNo) = 1 Then

Page 180

176

intTotalSelectItemNo = intTotalSelectItemNo - 1

lblBuyCount.Caption = intTotalSelectItemNo

End If

MoveDate intCurrentItemNo

End Sub

Private Sub ItemCheck1_Click()

If ((intCurrentPageNo - 1) * 8 + 1) > intTotalItem Then

ItemCheck1.Value = 0

Exit Sub

End If

intItemCheck((intCurrentPageNo - 1) * 8 + 1) = ItemCheck1.Value

If ItemCheck1.Value = 1 Then

intTotalSelectItemNo = intTotalSelectItemNo + 1

Else

intTotalSelectItemNo = intTotalSelectItemNo - 1

Page 181

177

End If

lblBuyCount.Caption = intTotalSelectItemNo

End Sub

/*標記欄位 1-8

Private Sub ItemCheck2_Click()

If ((intCurrentPageNo - 1) * 8 + 2) > intTotalItem Then

ItemCheck2.Value = 0

Exit Sub

End If

intItemCheck((intCurrentPageNo - 1) * 8 + 2) = ItemCheck2.Value

If ItemCheck2.Value = 1 Then

intTotalSelectItemNo = intTotalSelectItemNo + 1

Else

intTotalSelectItemNo = intTotalSelectItemNo - 1

End If

lblBuyCount.Caption = intTotalSelectItemNo

Page 182

178

End Sub

Private Sub ItemCheck3_Click()

If ((intCurrentPageNo - 1) * 8 + 3) > intTotalItem Then

ItemCheck3.Value = 0

Exit Sub

End If

intItemCheck((intCurrentPageNo - 1) * 8 + 3) = ItemCheck3.Value

If ItemCheck3.Value = 1 Then

intTotalSelectItemNo = intTotalSelectItemNo + 1

Else

intTotalSelectItemNo = intTotalSelectItemNo - 1

End If

lblBuyCount.Caption = intTotalSelectItemNo

End Sub

Private Sub ItemCheck4_Click()

Page 183

179

If ((intCurrentPageNo - 1) * 8 + 4) > intTotalItem Then

ItemCheck4.Value = False

Exit Sub

End If

intItemCheck((intCurrentPageNo - 1) * 8 + 4) = ItemCheck4.Value

If ItemCheck4.Value = 1 Then

intTotalSelectItemNo = intTotalSelectItemNo + 1

Else

intTotalSelectItemNo = intTotalSelectItemNo - 1

End If

lblBuyCount.Caption = intTotalSelectItemNo

End Sub

Private Sub ItemCheck5_Click()

If ((intCurrentPageNo - 1) * 8 + 5) > intTotalItem Then

ItemCheck5.Value = 0

Exit Sub

Page 184

180

End If

intItemCheck((intCurrentPageNo - 1) * 8 + 5) = ItemCheck5.Value

If ItemCheck5.Value = 1 Then

intTotalSelectItemNo = intTotalSelectItemNo + 1

Else

intTotalSelectItemNo = intTotalSelectItemNo - 1

End If

lblBuyCount.Caption = intTotalSelectItemNo

End Sub

Private Sub ItemCheck6_Click()

If ((intCurrentPageNo - 1) * 8 + 6) > intTotalItem Then

ItemCheck6.Value = 0

Exit Sub

End If

intItemCheck((intCurrentPageNo - 1) * 8 + 6) = ItemCheck6.Value

Page 185

181

If ItemCheck6.Value = 1 Then

intTotalSelectItemNo = intTotalSelectItemNo + 1

Else

intTotalSelectItemNo = intTotalSelectItemNo - 1

End If

lblBuyCount.Caption = intTotalSelectItemNo

End Sub

Private Sub ItemCheck7_Click()

If ((intCurrentPageNo - 1) * 8 + 7) > intTotalItem Then

ItemCheck7.Value = False

Exit Sub

End If

intItemCheck((intCurrentPageNo - 1) * 8 + 7) = ItemCheck7.Value

If ItemCheck7.Value = 1 Then

intTotalSelectItemNo = intTotalSelectItemNo + 1

Else

Page 186

182

intTotalSelectItemNo = intTotalSelectItemNo - 1

End If

lblBuyCount.Caption = intTotalSelectItemNo

End Sub

Private Sub ItemCheck8_Click()

If ((intCurrentPageNo - 1) * 8 + 8) > intTotalItem Then

ItemCheck8.Value = False

Exit Sub

End If

intItemCheck((intCurrentPageNo - 1) * 8 + 8) = ItemCheck8.Value

If ItemCheck8.Value = 1 Then

intTotalSelectItemNo = intTotalSelectItemNo + 1

Else

intTotalSelectItemNo = intTotalSelectItemNo - 1

Page 187

183

End If

lblBuyCount.Caption = intTotalSelectItemNo

End Sub

Private Sub cmdClear_Click()

Dim intTemp As Integer

For intTemp = 1 To 60

strItemName(intTemp) = ""

intItemMoney(intTemp) = ""

intItemCheck(intTemp) = 0

Next

intTotalItem = 0

intCurrentItemNo = 1

intCurrentPageNo = 1

lngTotalMoney = 0

intTotalSelectItemNo = 0

Page 188

184

lblBuyCount.Caption = intTotalSelectItemNo

lblShopMoney.Caption = lngTotalMoney

lblTotalCount.Caption = intTotalItem

lblMessage.Caption = ""

ClearList

DisplayListData intCurrentPageNo

End Sub

/*將所有資料清除

Private Sub Form_OKClick()

App.End

End Sub

/*程式結束

Page 189

185

C.介紹如何將模擬器程式製作成安裝檔 ﹐將其安裝到 PDA 上 。

使用 EVB 寫出來的程式要安裝到 PDA 內部 ﹐變成內部執行程

式必須要將程式碼做成安裝檔 ﹐首先將程式打開 ﹐在 FILE 選項內 ﹐

有個 Make *****.vb 的指令 ﹐

做出來的 vb 檔案等等會用到 ﹐所以先放在明顯的地方 ﹐如桌面等

等 。 接下來就是製作安裝檔 ﹐在 Tools 內的 Remote Tools 下的

Application Install Wizard 。

Page 190

186

點擊進入 。

確定進入下一步 。

所指定的位置必須為該程式的*.ebp 檔案所在位置 。 確認後進行下

一步 。

Page 191

187

這個檔案就是一開始製作的 vb 檔 ﹐在這邊要把它載入 。

接下來就是選擇位置放置製作的安裝檔案 。

Page 192

188

這個功能是選擇支援哪種電腦 。

如果程式內使用到任何資源 ﹐都要把它載入 。

Page 193

189

是否有任何附加檔案 。

接下來就是對這安裝檔的敘述 。

Page 194

190

接下來按下 Create Install 就開始製作安裝檔 。

這就是做出來的全部安裝檔 ﹐如果要安裝 ﹐請進入 CD1 的資料夾 。

點擊 Setup 就會將程式安裝到 PDA 中 。