TweetFollow Us on Twitter

Derived Classes
Volume Number:10
Issue Number:9
Column Tag:Getting Started

C++, Derived Classes, and ObjectMaster

By Dave Mark, MacTech Magazine Regular Contributing Author

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

Back in January’s column, we took a look at some of the basic concepts behind C++. We talked about objects, and the fact that the variables you’d find in a C program are called objects in a C++ program. We talked about classes, and the class and struct keywords used to declare them. We talked about data members, the fields found in a class, and member functions, the functions associated with a class.

We talked about the different methods of creating an object, from declaring an object to creating one in the heap using the new operator. We also covered the delete operator, used to free up the memory used by an object. We talked about constructors and destructors, the member functions that are called when an object is created and destroyed.

We also talked about the public and private access specifiers, used to determine whether a non-member function has access to a specific class member.

If any of these topics make your eyes roll up in their sockets, take a look back at the January ‘94 C++ column. If you don’t have the January issue, you can order a back-issue from MacTech...

In this month’s column, we’ll take all this one step further. We’ll take a look at class derivation, the technique of basing a new class on an existing class. We’ll also learn about ObjectMaster, a cool tool that makes derived classes a lot easier to work with.

Even if you’ve already read “Learn C++ on the Macintosh”, keep reading. Though the beginning of this column covers some material you’ve seen in the chapter on Derived Classes, this column has a lot of new stuff in it. Most importantly, this month’s sample program shows you how to set up a multi-file C++ program, something that “Learn C++” never did...

Derived Classes

C++ allows you to use one class declaration, known as a base class, as the basis for the declaration of a second class, known as a derived class. Suppose you had a class called CPushButton that implemented a simple push-button, like the one shown in Figure 1.

Figure 1. Simple push-button.

The CPushButton class definition might look something like this:


/* 1 */
class CPushButton
{
 protected:
 Rect   bounds;
 Str255 title;
 BooleanisDefault;
 WindowPtrowningWindow;
 ControlHandle buttonControl;

 public:
 CPushButton( WindowPtr owningWindow, Rect *bounds,
 Str255 title, Boolean isDefault );
 virtual~CPushButton();
 virtual void  Draw();
 virtual void  DoClick();
};

For the moment, ignore the keyword virtual that’s sprinkled throughout the CPushButton class definition. We’ll get to it in a bit.

Take a look at the CPushButton data members. Notice that they were declared using the protected access specifier. protected is very similar to the private access specifier. A data member or member function marked as private is only accessible from within member functions of that class. For example, if you defined an object in main() that featured a private data member, referring to the data member within a member function works just fine, but referring to the data member within main() will cause a compile error, telling you that you are trying to access a member inappropriately.

A member marked as protected is accessible from member functions of the class and also from member functions of any classes derived from that class. For example, the bounds data member might be accessed by the CPushButton classes’ Draw() function, but never by an outside function like main().

In addition, a protected member is also accessible from the member functions of any classes derived from its class.

Deriving One Class From Another

Why would you want to derive one class from another? You might want to extend the functionality of a class, or customize it in some way. For example, you might create a CPictureButton class that creates a push button with a picture instead of a text label. By basing the CPictureButton class on CPushButton, you automatically inherit all of CPushButton data members and member functions.

Here’s my definition of a CPictureButton class:


/* 2 */
class CPictureButton : public CPushButton
{
 protected:
 PicHandlepic;

 public:
 CPictureButton( WindowPtr owningWindow, Rect *bounds,
 PicHandle pic, Boolean isDefault );
 virtual~CPushButton();
 virtual void  Draw();
 virtual void  DoClick();
};

The first line tells you that this class is derived from the CPushButton class. Every CPictureButton object you create will inherit all the CPushButton member functions and data members. Here’s what this means.

When you create a CPushButton object, space is allocated for the CPictureButton data member, as well as for all of the non-private CPushButton data members. This means that a CPictureButton member function can refer to both CPictureButton and CPushButton data members. Here’s a CPictureButton definition:


/* 3 */
CPictureButton   *myPictureButton;

myPictureButton = new CPictureButton(myWindow, &buttonRect, 
 myPicture, true );

A myPictureButton member function might refer to pic, which is a CPictureButton data member, or perhaps bounds or owningWindow, which were provided courtesy of CPushButton. The point here is, the CPictureButton class didn’t need to define the data members that gave it its infrastructure. All it needed were the members that provided what wasn’t already provided by its base class.

If you declared a third class based on CPictureButton, the new class would inherit the members all the way up the inheritance chain, from both CPictureButton and CPushButton.

Access to member functions follow the same rules as access to data members. In the previous code, the CPictureButton member functions can access all of the non-private CPushButton member functions. Why non-private? Though your derived class does inherit all of the base class members, the compiler won’t let you access inherited private members.

To prove this yourself, add a private data member to the CGramps class in this month’s program, then try to reference it from the class derived from CGramps, which is CPops.

My general strategy is to mark my member functions as public and my data members as protected. If for some reason you don’t want to mark your member functions as public, be sure you at least mark your constructor and destructor as public, otherwise you won’t have the access you need to create your object!

In the definition of the CPictureButton class above, you may have noticed the public keyword on the first line:

class CPictureButton : public CPushButton

This keyword tells the compiler how the members of the base class are accessed by the derived class. The public keyword says that public members from the base class are public in the derived class, protected members from the base class are protected in the derived class, and private members are not accessible at all.

If the private keyword is used, public and protected members from the base class are private in the derived class, and private members are not inherited at all.

You don’t have to include the access keyword. If the base class was defined using struct, the derivation defaults to public. If the base class was defined using class, the derivation defaults to private. My preference is to use class to define all my classes and to use the public keyword in my derived class definitions.

Mapping Constructor Parameters

When you define an object of a derived class, the base classes’ constructor is called and then the derived classes’ constructor is called. The base classes’ constructor initializes its the base classes’ data members and the derived classes’ constructor initializes the derived classes’ data members.

When you create a derived class object, you provide the parameters you want passed to the derived classes’ constructor:


/* 4 */
CPictureButton   *myPictureButton;

myPictureButton = new CPictureButton(myWindow, &buttonRect, 
 myPicture, true );

The question is, what parameters get passed to the base class constructor? The answer lies in the definition of the derived classes’ constructor. The format of the base classes’ constructor call are specified in the derived constructor’s title line. For example:


/* 5 */
CPictureButton::CPictureButton(   WindowPtr owningWindow, 
 Rect *bounds, PicHandle pic, Boolean isDefault )
 : CPushButton( owningWindow, bounds, "\p", isDefault );

Notice the colon (“:”), followed by the CPushButton constructor call. Three of the CPushButton() parameters come from parameters passed to CPictureButton(). The parameter pic wasn’t passed on to CPushButton(), and the title, which wasn’t used by CPictureButton() was specified using the literal "\p".

Suppose you derived yet another class from CPictureButton(). This new constructor would get called after CPictureButton(), which got called after CPushButton(). The new constructor would map its parameters to the CPictureButton constructor. CPictureButton() would continue to map its parameters to CPushButton().

Virtual Member Functions

Earlier in the column, I promised an explanation of the mysterious virtual keyword. Take another look at the CPictureButton class definition:


/* 6 */
class CPictureButton : public CPushButton
{
 protected:
 PicHandlepic;

 public:
 CPictureButton(  WindowPtr owningWindow, Rect *bounds,
  PicHandle pic, Boolean isDefault );
 virtual~CPushButton();
 virtual void  Draw();
 virtual void  DoClick();
};

Think about the mechanics of creating a CPictureButton, that is, a push-button with a picture instead of a text string. The constructor will create the push-button control and attach it to the specified window. The Draw() routine will draw the button outline, a default border (if isDefault is true) and center a PICT in the button. So far, no problem.

What does DoClick() do? If you want CPictureButton() to serve as a general purpose, reusable class, you can’t make DoClick() do anything very specific. The solution is to derive a new class from CPictureButton(), and provide a custom DoClick() that does exactly what you want when the button is pressed. Here’s how this works.

If a derived class declares the same function as its base class, the derived classes’ function overrides the corresponding base class function. Suppose you derived a class from CPictureButton named CMyPictureButton. If CPictureButton and CMyPictureButton both feature a function named DoClick() with the same parameter list, CMyPicture::DoClick() overrides CPicture::DoClick().

Now suppose you define a CMyPictureButton object, like this:


/* 7 */
CPictureButton   *buttonPtr;

buttonPtr = new CMyPictureButton( /* put params here */ );

When buttonPtr->DoClick() is called, which version of DoClick() will get called? Before this question is answered, take another look at the definition. The pointer was declared as a pointer to the base class, while the new object belongs to the derived class.

If the base class version, CPictureButton::DoClick(), is not marked as virtual, a reference to buttonPtr->DoClick() will cause CPictureButton::DoClick() to get called. If CPictureButton::DoClick() is marked as virtual, the reference to buttonPtr->DoClick() will cause the derived version, CMyPictureButton::DoClick() to get called.

The idea here is that your development environment will likely include classes and member functions designed specifically to be overridden. All you need to do is base your own classes on existing classes and override the member functions you want to bend to your will. You might like the CPictureButton Draw() function and decide not to provide one of your own. You might prefer to have the PICT scaled inside the button instead of clipped in its natural size. All you have to do is override the Draw() member function, providing one that does exactly what you want. Since the base class Draw() is virtual, if you provide one in your derived class it will get called instead of the base Draw(). If you don’t provide one, the base class Draw() will get called instead.

Your overriding function can also call the function it overrides. Suppose the overridden Draw() function draws the outline of a button as well as the default outline if appropriate. Your overriding Draw() function might call the base Draw() first to get the basic button drawn, then do its specialty drawing over that. To call a member function from your base class, just include the base class name and two colons (“::”) followed by the function call. For example, to call CPictureButton’s version of Draw(), call:


/* 8 */
CPictureButton::Draw()

Note that you can call the overridden function before you do your thing, or after. Your approach will depend on the class design.

The code samples you’ve seen in this column are not meant to reflect a specific class library. If you take a look at the CButton class in the TCL, you’ll see that it uses a far more complex mechanism to respond to button clicks. The virtual keyword does work the same way, though. Whether you use MacApp, TCL, PowerPlant, or whatever, C++ is C++. Pick a class you are pretty comfortable with and look through its .cp and .h files. Chances are you’ll find that your class is derived from some base class, and that it features a few virtual member functions, as well.

A Sample Program

Enough theory already - let’s get to some code! The following program is based on the Gramps program from Chapter 6 of “Learn C++ on the Macintosh”. The original Gramps consisted of three classes and a main(), a folded into a single file. In this version, we’ll do things the more traditional way. We’ll break Gramps up into seven source code files. Each class definition is placed in its own “.h” file, while the member functions for that class are gathered in a “.cp” file. Finally, we’ll put the source for main() in a file called “main.cp”. Doing this will make your C++ programs much more manageable and will help you make more sense of class libraries like MacApp, TCL, and PowerPlant.

Create a folder named Gramps in your development folder. Launch the THINK Project Manager or CodeWarrior (whichever you are using) and create a new project named Gramps.Π in the Gramps folder. The next few sections walk you through the creation of the seven Gramps source code files...

1) CGramps.h - Create a new source code file and type in the following source code:


/* 9 */
#ifndef __CGramps__
#define __CGramps__

CGramps
class CGramps
{
// Data members...
 protected:
 int    grampsDataMember;
// Member functions...
 public:
 CGramps();
 virtual~CGramps();
};
#endif

Save the source code as CGramps.h and close the file.

2) CGramps.cp - Create a new source code file and type in the following source code:


/* 10 */
#include <iostream.h>
#include "CGramps.h"

CGramps::CGramps()
{
 grampsDataMember = 1;
 
 cout << "CGramps' constructor was called!\n";
}

CGramps::~CGramps()
{
 cout << "CGramps' destructor was called!\n";
}

Save the source code as CGramps.cp and add it to the project, then close the file.

3) CPops.h - Create a new source code file and type in the following source code:


/* 11 */
#ifndef __CPops__
#define __CPops__

#ifndef __CGramps__
#include "CGramps.h"
#endif

CPops:CGramps
class CPops : public CGramps
{
// Data members...
 protected:
 int    popsDataMember;
// Member functions...
 public:
 CPops();
 virtual~CPops();
};
#endif

Save the source code as CPops.h and close the file.

4) CPops.cp - Create a new source code file and type in the following source code:


/* 12 */
#include <iostream.h>
#include "CPops.h"
CPops::CPops()
CPops::CPops()
{
 grampsDataMember = 1000;
 cout << "CPops' constructor was called!\n";
}

CPops::~CPops()
{
 cout << "CPops' destructor was called!\n";
}

Save the source code as CPops.cp and add it to the project, then close the file.

5) CJunior.h - Create a new source code file and type in the following source code:


/* 13 */
#ifndef __CJunior__
#define __CJunior__

#ifndef __CPops__
#include "CPops.h"
#endif

CJunior:CPops
class CJunior : public CPops
{
// Data members...
 protected:
 int    juniorDataMember;

// Member functions...
 public:
 CJunior();
 virtual~CJunior();
};

#endif

Save the source code as CJunior.h and close the file.

6) CJunior.cp - Create a new source code file and type in the following source code:


/* 14 */
#ifndef __CJunior__
#include "CJunior.h"
#endif

#include <iostream.h>
CJunior::CJunior()
CJunior::CJunior()
{
 cout << "CJunior's constructor was called!\n";
 cout << "grampsDataMember == " << grampsDataMember << "\n";
}

CJunior::~CJunior()
{
 cout << "CJunior's destructor was called!\n";
}

Save the source code as CJunior.cp and add it to the project, then close the file.

7) main.cp - Create a new source code file and type in the following source code:


/* 15 */
#include <iostream.h>
#include "CJunior.h"

main
intmain()
{
 CJunior*juniorPtr;
 
 juniorPtr = new CJunior;
 cout << "----\n";
// juniorPtr->grampsDataMember = 1; <--This won't compile
 delete juniorPtr;
 return 0;
}

Save the source code as main.cp and add it to the project, then close the file.

If you use Symantec C++, you might want to add the .h files to your project. Doing this gives you quick access to the .h files since they appear in the list of files in the project window. If you do this, be sure to select Extensions from the THINK Project Manager preferences’ popup menu and set the .h translator to <<None>> (Figure 2). That way, the TPM won’t try to compile your .h files, yet they can still appear in the project window.

Figure 2. The THINK Project Manager’s Extensions page.

As far as I know, there’s no way to do this with CodeWarrior. On the other hand, the CodeWarrior project window features a popup menu (see Figure 3) that gives you access to all of your include files, similar to an option-click in the THINK project window’s title bar.

Figure 3. The CodeWarrior popup
that gives you access to your include files.

Your next step is to add the libraries that your development environment needs to build an iostream based C++ program. If you’re using Symantec C++, you’ll need to add the libraries IOStreams, ANSI++, and CPlusLib. My Symantec C++ project window is shown in Figure 4.

Figure 4. The Symantec C++ project window.

If you’re using CodeWarrior, you’ll want to add the libraries CPlusPlusLib, “ANSI (2i) C++.68K.Lib”, and “ANSI (2i) C.68K.Lib”. Substitute the PowerPC versions of these libraries if you are running the PowerPC version of CodeWarrior.

Figure 5. The CodeWarrior 68K project window.

Running Gramps

Regardless of your development environment, when you run the program your output should look the same. Here’s mine:


/* 16 */
CGramps' constructor was called!
CPops' constructor was called!
CJunior's constructor was called!
grampsDataMember == 1000
----
CJunior's destructor was called!
CPops' destructor was called!
CGramps' destructor was called!

Gramps consists of three classes. CJunior is derived from CPops, and CPops is derived from CGramps. The program is pretty simple and is really provided as a laboratory you can use for your own experiments. Once you understand the relationships between the three classes, try your hand at modifying the classes. Add a virtual function to CPops and an overriding function with the same name to CJunior. Experiment.

main() starts off by creating a CJunior object:


/* 17 */
#include <iostream.h>
#include "CJunior.h"

main
intmain()
{
 CJunior*juniorPtr;
 
 juniorPtr = new CJunior;

This one line of code causes a sequence of constructor calls, starting with the CGramps constructor and ending with the CJunior constructor. Next, a separator line is printed.


/* 18 */
 cout << "----\n";

Try uncommenting this next line of code by moving the // to just after the semicolon. You should get a compile error, telling you that grampsDataMember is not accessible from main(). This makes sense, since grampsDataMember is marked as protected, and main() is not in the CGramps inheritance chain.


/* 19 */
// juniorPtr->grampsDataMember = 1; <--This won't compile

Finally, we delete the CJunior object. This causes a sequence of three destructor calls, in the reverse order that the constructors were called.


/* 20 */
 delete juniorPtr; 
 return 0;
}

As you look through the source code, follow the path of the CGramps data member, grampsDataMember. The CGramps constructor sets grampsDataMember to 1, and the CPops constructor sets grampsDataMember to 1000. The CJunior constructor prints out the value of grampsDataMember. As you can see by the output, grampsDataMember ends up with a value of 1000, showing that CPops() was called after CGramps() and that all three constructors have access to the same data member.

As you look through the three .h files, you’ll notice several lines that start with #ifndef. For example, CPops.h starts off with:


/* 21 */
#ifndef __CPops__
#define __CPops__

#ifndef __CGramps__
#include "CGramps.h"
#endif

and ends with a single #endif. Without these lines, a file that included both CGramps.h and CPops.h to get access to both class definitions would end up including the CGramps class twice. Once you understand why this works, you’ll want to use a similar technique in all your .h files. Be sure to check out the difference between a base class .h file like CGramps.h and a derived class .h file like CPops.h.

Enter ObjectMaster

Before we go, I’d like to take a quick look at a great programming tool called ObjectMaster. ObjectMaster was originally built as an internal programmer’s tool by ACI, the people who brought us Fourth Dimension. It made life so much easier for object programming, they released it as a product.

ObjectMaster is an object browser and editor you can use to create and edit all your classes. ObjectMaster uses a project structure that parallels those of Symantec C++ and CodeWarrior. Figure 6 shows my ObjectMaster project window for Gramps. Notice that all the source code files, including the .h files are listed.

Figure 6. The Gramps ObjectMaster project window.

Figure 7 shows a browser window, listing all the classes that make up Gramps. Take a look at the upper-left pane. The pane starts by listing the Gramps classes, using indentation to show that CPops is derived from CGramps and CJunior from CPops. ••Globals lists any global objects or functions including class definitions. Finally, ••Structures lists any global structure definitions (most appropriate for non-OOP languages like C).

Figure 7. An ObjectMaster browser.

In the upper left pane, I’ve clicked on the CJunior class. The CJunior member functions are listed in the middle pane and the data members are listed in the upper-right pane. When I double-clicked on the CJunior constructor in the middle pane, the source for CJunior() appears in the bottom pane.

The browser window is incredibly powerful. I used it to create all my source code files, as well as all my classes and member functions. If you look just under the title bar of the browser window you’ll see a mini-menubar filled with powerful commands. Figure 8 shows the Filing mini-menu.

Figure 8. The Filing mini-menu.

To create a new derived class, I selected the base class in the upper-left pane, then selected New Class... from the Filing mini-menu. In this case, I was creating the CJunior class. I told ObjectMaster to place the class definition in the file CJunior.h and the constructor and destructor in CJunior.cp. ObjectMaster automatically set up both files and added them to the project. All I had to do was add in any extra member functions or data members.

Figure 9. The New Class... dialog.

I’ve really just barely scratched the surface of ObjectMaster. Once I used it to create all my source code files, I had it automatically add all the source code files to my Symantec C++ project. Using Apple events, ObjectMaster can tell your development environment to compile and run your application. It can even syntax check your source code for you.

ObjectMaster is compatible with MPW, THINK, and CodeWarrior environments, and knows how to read C, C++, Object Pascal, and Modula2. It comes in two forms, ObjectMaster Universal, which supports all of the mentioned environments, and ObjectMaster, which supports MetroWerks and Symantec C++, but not MPW.

ObjectMaster Universal is available for a street price of about $289 and ObjectMaster is about $189. If you don’t use Object Pascal and you don’t use MPW, regular ObjectMaster should do you just fine.

Till Next Month...

I’m really interested in your feelings about this month’s column. Would you like more C++ coverage? Should I spend a little time on the TCL? Would you like me to write about some of the newer technologies, like those that are part of System 7.5? Send me some e-mail and let me know what you’d like to see in future columns.

In the meantime, I’d better get going. Daniel’s got a bunch of new books and he’s got the entire evening’s reading laid out for me. See you next month...

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Fantastical 2.5.13 - Create calendar eve...
Fantastical is the Mac calendar you'll actually enjoy using. Creating an event with Fantastical is quick, easy, and fun: Open Fantastical with a single click or keystroke Type in your event... Read more
A Better Finder Rename 11.05 - File, pho...
A Better Finder Rename is the most complete renaming solution available on the market today. That's why, since 1996, tens of thousands of hobbyists, professionals and businesses depend on A Better... Read more
SpamSieve 2.9.38 - Robust spam filter fo...
SpamSieve is a robust spam filter for major email clients that uses powerful Bayesian spam filtering. SpamSieve understands what your spam looks like in order to block it all, but also learns what... Read more
TeamViewer 15.0.8397 - Establish remote...
TeamViewer gives you remote control of any computer or Mac over the Internet within seconds or can be used for online meetings. Find out why more than 200 million users trust TeamViewer! Free for non... Read more
SteerMouse 5.4.3 - Powerful third-party...
SteerMouse is an advanced driver for USB and Bluetooth mice. SteerMouse can assign various functions to buttons that Apple's software does not allow, including double-clicks, modifier clicks,... Read more
Toast Titanium 18.2.1 - The ultimate med...
Roxio Toast Titanium, the leading DVD burner for Mac, makes burning even better, adding Roxio Secure Burn to protect your files on disc and USB in Mac- or Windows-compatible formats. Get more style... Read more
HoudahSpot 5.0.11 - Advanced file-search...
HoudahSpot is a versatile desktop search tool. Use HoudahSpot to locate hard-to-find files and keep frequently used files within reach. HoudahSpot will immediately feel familiar. It works just the... Read more
ClipGrab 3.8.6 - Download videos from Yo...
ClipGrab is a free downloader and converter for YouTube, Vimeo, Facebook and many other online video sites. It converts downloaded videos to MPEG4, MP3 or other formats in just one easy step Version... Read more
ExpanDrive 7.4.0 - Access cloud storage...
ExpanDrive builds cloud storage in every application, acts just like a USB drive plugged into your Mac. With ExpanDrive, you can securely access any remote file server directly from the Finder or... Read more
Adobe Dreamweaver CC 2020 20.0 - Build w...
Dreamweaver CC 2020 is available as part of Adobe Creative Cloud for as little as $20.99/month (or $9.99/month if you're a previous Dreamweaver customer). Adobe Dreamweaver CC 2020 allows you to... Read more

Latest Forum Discussions

See All

New heroes and balance updates set to ar...
It feels like Hearthstone: Battlegrounds only launched yesterday, and already the auto batter addition to Blizzard's megahit card game is set to receive new heroes and balance updates. [Read more] | Read more »
Pre-register for Hello Kitty AR: Kawaii...
Hello Kitty — the cute cat that launched a multi-billion-pound franchise — has been brought to life… sort of. Sanrio has teamed up with the Bublar Group to create a new mobile game that uses AR tech to turn the real world into Hello Kitty’s... | Read more »
Gorgeous and tranquil puzzler Spring Fal...
One-man indie studio SPARSE//GameDev has now launched its tranquil puzzler, Spring Falls. It's described as "a peaceful puzzle game about water, erosion, and watching things grow". [Read more] | Read more »
Black Desert Mobile gets an official rel...
Pearl Abyss has just announced that its highly-anticipated MMO, Black Desert Mobile, will launch globally for iOS and Android on December 11th. [Read more] | Read more »
Another Eden receives new a episode, cha...
Another Eden, WFS' popular RPG, has received another update that brings new story content to the game alongside a few new heroes to discover. [Read more] | Read more »
Overdox guide - Tips and tricks for begi...
Overdox is a clever battle royale that changes things up by adding MOBA mechanics and melee combat to the mix. This new hybrid game can be quite a bit to take in at first, so we’ve put together a list of tips to help you get a leg up on the... | Read more »
Roterra Extreme - Great Escape is a pers...
Roterra Extreme – Great Escape has been described by developers Dig-It Games as a mini-sequel to their acclaimed title Roterra: Flip the Fairytale. It continues that game's tradition of messing with which way is up, tasking you with solving... | Read more »
Hearthstone: Battlegrounds open beta lau...
Remember earlier this year when auto battlers were the latest hotness? We had Auto Chess, DOTA Underlords, Chess Rush, and more all gunning for our attention. They all had their own reasons to play, but, at least from where I'm standing, most... | Read more »
The House of Da Vinci 2 gets a new gamep...
The House of Da Vinci launched all the way back in 2017. Now, developer Blue Brain Games is gearing up to deliver a second dose of The Room-inspired puzzling. Some fresh details have now emerged, alongside the game's first official trailer. [Read... | Read more »
Shoot 'em up action awaits in Battl...
BattleBrew Productions has just introduced another entry into its award winning, barrelpunk inspired, BattleSky Brigade series. Whilst its previous title BattleSky Brigade TapTap provided fans with idle town building gameplay, this time the... | Read more »

Price Scanner via MacPrices.net

Apple continues to offer 2017 13″ Dual-Core n...
Apple has Certified Refurbished 2017 13″ 2.3GHz Dual-Core non-Touch Bar MacBook Pros still available starting at $1019. An standard Apple one-year warranty is included with each model, outer cases... Read more
Save up to $120 on the new 16″ MacBook Pro at...
Apple’s resellers are starting to receive stock of new 16″ MacBook Pros, and the first set of sales & deals are now available: (1) Amazon 16″ MacBook Pros start on sale for $100-$116 off Apple’s... Read more
Apple Watch Series 3 models on sale at Amazon...
Amazon has Apple Watch Series 3 GPS models on sale for $30 off MSRP, starting at only $169. There prices are the lowest we’ve ever seen for these models from any Apple reseller. Choose Amazon as the... Read more
The ‘Mac Potpourri’ Mailbag: Edition #1- Info...
COMMENTARY: 11.20.19- Welcome to the inaugural edition of the “Mac Potpourri” Mailbag where we take a look at correspondence received from readers of this column from all over the world who write in... Read more
13″ 2.4GHz MacBook Pros available for up to $...
Apple has a full line of Certified Refurbished 2019 13″ 2.4GHz 4-Core Touch Bar MacBook Pros available starting at $1529 and up to $300 off MSRP. Apple’s one-year warranty is included, shipping is... Read more
New at T-Mobile: Switch to T-Mobile, and get...
T-Mobile is offering a free 64GB iPhone 8 for new customers who switch to T-Mobile and open a new line of service. Eligible trade-in required, and discount applied over a 24 month period. The fine... Read more
Xfinity Mobile’s Black Friday Apple savings:...
Take $250 off the purchase of any iPhone at Xfinity Mobile with a new line activation, and transfer of phone number to Xfinity Mobile, through December 8, 2019. This includes Apple’s new iPhone 11... Read more
2019 13″ 1.4GHz MacBook Pros available starti...
Apple has a full line of Certified Refurbished 2019 13″ 1.4GHz 4-Core Touch Bar MacBook Pros available starting at $1099 and up to $230 off MSRP. Apple’s one-year warranty is included, shipping is... Read more
Save up to $350 on a 21″ or 27″ iMac with the...
Apple has Certified Refurbished 2019 21″ & 27″ iMacs available starting at $929 and up to $350 off the cost of new models. Apple’s one-year warranty is standard, shipping is free, and each iMac... Read more
Early Holiday 2019 Sale: B&H again offers...
B&H Photo has 10.2″ iPads on sale again for $30 off Apple’s MSRP, starting at $299, as part of their early Holiday 2019 sale. Overnight shipping is free to many addresses in the US: – 10.2″ 32GB... Read more

Jobs Board

Best Buy *Apple* Computing Master - Best Bu...
**745516BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Store Associates **Store NUmber or Department:** 001101-Manhattan-Store **Job Read more
Best Buy *Apple* Computing Master - Best Bu...
**746655BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Sales **Store NUmber or Department:** 002518-Atlantic Center-Store **Job Description:** Read more
*Apple* Mobility Pro - Best Buy (United Stat...
**747089BR** **Job Title:** Apple Mobility Pro **Job Category:** Store Associates **Store NUmber or Department:** 000377-Prescott AZ-Store **Job Description:** At Read more
*Apple* Health Benefit Specialist - Call Cen...
Description ** Apple Health Benefit Specialist - Call Center (MAS 3/MACSC)** **Olympia, WA Multiple Positions** *The ideal candidate for this position will have Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States- Apple Blossom Mall 1850 Apple Blossom Dr Job ID:1065040Salon Professionals Job Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.