Java IO
Java’s IO classes offer a surprising amount of functionality; it’s kind of surprising that they seem unappreciated in practice. The documentation on them rarely extends beyond simple file I/O. File I/O is usually what you use them for, of course, but the package offers alot more than simply reading and writing to files.
Streams are the Fundamental Abstraction of java.io.*
java.io consists of around 50 separate classes, and you can divide them up in a few ways. Almost all deal with the input or output of a stream. A stream really isn’t anything formal; it’s just a sequence of bytes that you interpret as data.
Streams may not necessarily be intuitive in some ways, though, so I’ll go over some of the concepts. Understanding these is critical to avoiding alot of the weird situations that you can find yourself in with streams.
Streams have no Implied Structure
The data contained inside a stream has no real indication of its structure; it’s up to the programmer to read it correctly. Java provides two sets of classes to deal with the two common forms of data: InputStream or OutputStream interpret data merely as bytes. Reader or Writer assume your data is textual, and they interpret data as a series of characters. These four abstract classes are the core of the IO package. Other classes supplement these to allow a wider range of expression and abstraction when dealing with data.
The Length of a Stream May Not Be Known
An easy analogy for streams is that they are like arrays. The stream contains a sequence of units of data, the programmer reads them in bits and pieces, and from them, the programmer gains useful information about something.
The analogy breaks down when it comes to finding a length. If you imagine an array, its size is known and fixed. You can access any piece of it at any time, instantly. The entire thing is immediately available. On the other hand, a stream’s size is not known. It’s assumed to end at some point, but may very well not depending on its source.
To belabor this point (and probably appear very patronizing), arrays are crayon boxes. You can pull out any given crayon without having to deal with any other crayons. Streams are like coke machines. You can access one unit of coke per request. Many different functions make this more convenient and faster, but in the end, you (or the methods you’re calling) are stuck dealing with one coke can at a time. You don’t know how many coke cans there are in the machine until the machine tells you it doesn’t have any more.
Of course, we’re talking about streams in the purest sense; for our examples, we know the length of the stream because we provide the data explicitly. The length of files may also be known since the array that we’re “streaming” from is local. So while a stream has no concept of its length, that doesn’t necessarily mean we never know its length. It just means that streams have no concept of their own size.
Streams Do Not Need To Immediately Return; They May Block
The power of streams is in their abstraction. Streams are merely sequences of data. The format is freeform, the length is not a concern, and the source need not be local. This flexibility has its limitations, however. The format must be explicitly given to the user of the stream, an unknown length requires care on the part of the programmer, and non-local sources may take a long time to return.
I’ve mentioned the first two, but the third point definitely requires some mention. A stream is dependent on its source for information. The speed at which that source provides our stream with data is not known, but we are ultimately dependent on that speed. While we’re waiting for that data, we must wait. This state of waiting is known as blocking – we’re blocking any further progress while we wait.
For an example, I’ll return to the crayon box/coke machine example. Getting a crayon from the crayon box is dependent only on how fast you yourself retrieve it. There’s no waiting because between the two concepts of “wanting the crayon” and “having the crayon,” you’re working towards the latter.
If you’re waiting in line at a coke machine, there’s still the two concepts of “wanting a coke” and “having a coke.” However, between these two concepts is a large amount of doing nothing. You’re waiting for other people to do stuff; you’re not working towards the goal directly. The coke machine and its users are blocking you from getting your coke.
There’s a couple clarifications to be made here when relating it to streams. First, the concept of blocking can occur with local things; accessing the hard drive is very expensive compared to accessing memory directly. In this example, your request for data on the hard drive will block until that data is available to use. Even if your request is the only one active, you’ll still have to wait for that process to succeed. Blocking simply means ‘the program cannot continue to execute until this method call successfully returns.’
Blocking Resources Do Not Always Need to Block
Blocking isn’t really an exact term. Waiting for a webpage to load is apparent blocking; you can’t use the webpage until it’s fully loaded. (Of course, there’s incremental loading of pages, but just work with me here.) You can see and feel the waiting. On the other hand, file I/O may not be perceivable. Large files, maybe, but small ones won’t. Memory access, processor cache access are all used with completely inapparent, but existent, wait-times between request and response.
These waits are blocking in the conceptual sense. Of course, we talk about blocking when it comes to network and file I/O but don’t talk about it when it comes to memory I/O. There are two reasons for this: The first is convention – file and network access take long enough to perceptibly block.
The other is more arcane: File and network access do not offer themselves exclusively to the requester. They may be servicing many concurrent requests at once, so we might be queued. These non-exclusive places require may require some form of blocking before they’re actually doing any work at all on our request. This is not true to exclusive resources; these service one person exclusively and constantly.
This last point is more important than it appears: The fact that non-exclusive resources may block means that they’re considering blocking resources. Whether they block on any given request is irrelevant. This marks a distinction between the concept of ‘blocking’ and the action of ‘blocking.’ I may not be blocking, but if I can block on some requests, I am a blocking resource.
Understanding blocking will save you alot of trouble when it comes to streams (or any sort of synchronous request). The fact that streams block means that stream is a potential bottleneck. A set of classes in java.io.* exist to minimize the strain these streams may cause to your performance.
read() Returns One Unit of Data from a Stream
The public interfaces of these classes mirror each other. The input classes mirror the output classes and the byte-based classes mirror their character-based ones, so learning one part of it lets you transfer that knowledge to any other set of classes. Very useful. I’ll start with read() and write().
Derivatives of Reader and InputStream function in the exact same way when dealing with simple I/O. Here’s an example to show this:
// Read one character from the stream
private static void doSomeCharIO() throws IOException {
Reader reader = new StringReader("This is a happy string.");
int firstValue = reader.read();
System.out.println(firstValue + " " + (char)firstValue);
// Output: "84 T"
}
This straightforward example is probably the smallest IO one can do. We create a StringReader which lets us treat a string as a stream. We then call reader.read() to get the first character. Notice that we don’t actually get a char back. Instead, we’re given an int that we have to cast.
One reason this doesn’t return a char is to be consistent with InputStream. (The other reason is negative values are indicative of errors; I’ll talk more later) At any rate, the lesson here is this: When using any input stream or reader, read() returns a single unit of data (a byte or character depending on the class used) that is always in terms of an integer. Casting that integer to a byte or character, respectively, will get you the data you want.
To prove my point that these two classes are basically the same, here’s the above example but using an InputStream:
// Read one byte from the stream
private static void doSomeByteIO() throws IOException {
byte[] byteArray = new byte[] { 'T', 'h', 'i' };
InputStream stream = new ByteArrayInputStream(byteArray);
int firstValue = stream.read();
System.out.println(firstValue + " " + (char)firstValue);
// Output: "84 T"
}
Since we’re using a different format, we need to use different classes. However, other than that, the two examples are identical.
write() is Symmetric to its Reading Counterpart
Write does what you’d expect: It writes a single byte or character. The signature of the simplest method is void write(int b). This signature is identical for both OutputStream and Writer. Since the potential range of values in a single char or byte is less than an int, you don’t need to explicitly cast them. Here’s the code for both, just to demonstrate:
private static void doSomeCharWriting() throws IOException {
int theValueOfT = (int)'T'; // Should be 84.
Writer writer = new StringWriter();
writer.write(theValueOfT);
System.out.println(writer); // Prints 'T'
}
private static void doSomeByteWriting() throws IOException {
int theValueOfT = (int)'T'; // Should be 84.
OutputStream stream = new ByteArrayOutputStream();
stream.write(theValueOfT);
System.out.println(stream); // Also prints 'T'
}
As you can see, it’s straightforward. We instantiate the correct class, write our value, then can send it directly to System.out. It should probably be noted that neither OutputStream nor Writer guarantee toString() will work in this way for all their derivatives; retrieving the value of a written stream may vary from class to class.
I’m getting a little self-conscious about the rate at which I’m introducing this stuff. I figure these mechanics seem fairly self-evident; my intention is to demonstrate these fundamentals fully before moving on to more complicated stuff. My hope is that this foundation will serve you well when you’re trying to understand the much more complicated stuff later, as you’ll have this to fall back on.
But after all, dealing with streams in terms of single units is not exactly common place. You’ll probably be dealing in terms of groups of bytes or characters. You may abstract even further and deal with streams in terms of primitives and objects. If your data has some parsing involved, you may need to navigate through the stream. Java’s facilities will serve you will in all these cases.
A Stream Can Work With Arrays of Data
read() and write() can understandably be given arrays as well as single units. The character-based and byte-based stream classes differ only in what type of array they’re expecting, so I’ll only demonstrate one such type here. This example will use a CharArrayReader just because we haven’t used one yet; there’s no motive beyond that.
private static void doSomeSimpleCharReading() throws IOException {
Reader reader = new CharArrayReader("This could be a character array!".toCharArray());
char[] charArray = new char[4];
reader.read(charArray);
System.out.println(charArray); // Prints "This"
reader.read(charArray);
System.out.println(charArray); // Prints " cou"
}
This little tidbit fills our charArray with as many bytes as it can hold (in this case, just 4). Notice that since we’re just dealing with data, the array we give it has no concept of being “filled” or not. You can see here that when we call read() again, we overwrite our previous contents. This can be fairly convenient when you’re dealing with data that comes in known quantities.
On the other hand, having it destroy your array every time it reads may not be what you want. Furthermore, the Reader will fill the array with n bytes, where n is the length of your array. You may not necessarily want it to take that many, but don’t want to make many arrays to handle all the different sizes. Java has more advanced capabilities to deal with this sitaution, but for the moment, the idea is useful to show the final version of write():
private static void doSomeExactCharReading() throws IOException {
Reader reader = new CharArrayReader(
"This could be a character array!".toCharArray()
);
char[] charArray = new char[10];
reader.read(charArray, 0, 4);
System.out.println(charArray); // Roughly prints "This"
reader.read(charArray, 5, 5);
System.out.println(charArray); // Roughly prints "This coul"
}
This version of read expects a char[] array for its first argument, like the second form. It also expects an offset and a length. These will be used to determine where to start writing in the array you’ve given it, and how many characters to fill. Notice that you’ll be given an IndexOutOfBoundsException if you try to overflow the array.
Streams Do Not Panic When Reaching the End Of A Stream
At some point, you’ll reach the end of your stream. Many languages throw an exception when this occurs. They provide no other means to determine whether you’ve reached this point, so you’re always having to catch it. This condition makes for clever code (since your EOF, or end-of-file for convenience here, logic is separated), but it’s hardly exceptional. I imagine it is merely the cleverness that justifies using the exception. This is an act grates on me.
Luckily for us, Java reacts calmly in the face of EOF. A tempting solution is to use a stream method called available(). This returns an integer that represents an estimate of how many bytes that can be read without blocking on the next invocation of a method for this input stream. It is NOT necessarily the length of the stream; as I mentioned earlier, streams are coke machines. We don’t necessarily know how long the stream is because the stream may not know. For example, one can’t open a file, make an array of the size that available() returns, and live happily ever after assuming that’s the full length of the file.
The definition I heartlessly stole from the API doc’s is very deliberately phrased. [[ To be continued... ]]
Streams Can Optionally Provide Mark/Reset Functionality
[[ To be continued... ]]
Swing, AWT, and SWT
I have heard many people discuss these GUI toolkits as though they are equal competitors. I’d like to state unequivocally that they are not. Use Swing. Use Swing.
Of course, what kind of advice would that be without backing it up with a long post? Without further ado, here’s details on the pros and cons of all three Java GUI toolkits, and how they compare.
AWT vs. Swing
This is one of those presumably old debates. It used to be relevant around ten years ago, but Swing and Java have both so drastically improved that there’s no reason to use AWT. Furthermore, the debate is somewhat moot since Swing is heavily based on AWT; a more apt title would be AWT vs. AWT with Swing-like sprinkles on top.
In fact, you can think of Swing as the successor to AWT rather than a competing toolkit. If you trace the class hierarchy of most Swing classes, you’ll find it has a AWT ancestor. Swing uses vast swaths of AWT functionality, so much that it’s safe to say Swing is built on top of AWT.
Heavyweight components have a native counterpart; lightweight components do not
Of course, both provide a set of ready-to-use GUI components, like buttons, menus, etc. The discussion of which toolkit to use usually means which set of GUI components to use, since the underlying framework in AWT is used directly by Swing.
It’s been commonly said that AWT’s components are “heavyweight” and Swing’s are “lightweight.” These terms do not necessarily mean either component is worse in terms of functionality or performance. They refer to whether a GUI component is “backed” by a native peer. You can think of a native peer as the “real” GUI component that the host platform would use normally. In other words, a native peer is the C or C++ class that you’d instantiate directly if you were programming in C/C++ exclusively.
The problem with this idea is that native peers necessarily vary between host platforms. Since AWT bases its functionality off these hosts, it is forced to the lowest common denominator of capability. This meant that relatively common components, like tables and trees, could not be included in AWT.
It should be mentioned that neither toolkit actually has a monopoly on either lightweight or heavyweight components. AWT is capable of creating lightweight components, and Swing must use heavyweight components when creating top-level components.
Swing has no native peers; it is pure Java
The limitations of AWT’s functionality led directly to Swing. Swing’s components are lightweight; they are pure Java and do not rely on the host platform. Swing’s components are therefore consistent in behavior across platforms.
Java’s aim is to be seamless, both to the developer and the end-user. The appearance of components should ordinarily look like the host platform’s. To solve this, Swing separated the behavior of components from their appearance. The appearance side is broken into many libraries, each implementing a different look and feel for each host platform.
Swing is, roughly, a superset of AWT
Swing is hardly written from scratch. Many components in Swings deliberately mirror their AWT counterparts. Other features use AWT classes directly. For example, all Swing components are derived from some AWT class, typically java.awt.Component.
Swing lies on top of AWT, adding a richness that the constraints of AWT did not allow. This freedom gives Swing the existing power of AWT with the flexibility to extend it as the designers and developers see fit. AWT’s components, on the other hand, are bound by the slow evolution of various host platforms.
There is no debate; use Swing
People usually see these two frameworks and imagine that there must be some choice to be made. After all, both are still around. However, there is no real choice. Swing is the successor to AWT. The reason AWT is still mentioned is because Swing uses an incredible amount of it.
It should be mentioned that there are no great performance gains anymore between AWT and Swing. Initially, AWT was respectably faster than Swing since native peers do allow performance gains in some cases. However, it’s been awhile since those times, and Swing and its companion libraries (like Java2D) have been optimized heavily. Performance is not a reason to use AWT over Swing.
SWT vs. Swing
Discussing Java toolkits should necessarily include this third-party toolkit, known as SWT or Standard Widget Toolkit. Popularized by its use in Eclipse, SWT can be thought of as a rewritten AWT, but with a Swing-fallback. SWT uses native peers if available but can fall back to pure Java components if a certain platform doesn’t support the features required. The reason for this extends beyond simple performance gains; SWT exposes a far greater amount of low-level functionality to the programmer. The intention is to allow a highly customizable interface for developers.
SWT is not standard
SWT has many forces working against it. For one, its name belies the fact that it’s not standard. SWT is a third-party toolkit not available on a vanilla Java install, nor is it necessarily compatible wherever Java can be run. Since it relies heavily on native integration, a SWT library must be written specifically for each native platform. The problem caused with this isn’t that you’ll be out of luck trying to run it on your machine (SWT already has most OS’s covered). Since SWT intentionally exposes alot of low-level capability, the experience may not be symmetric across these platforms. Reconciling these issues is left to the developer.
Swing’s continuing maturity removes many justifications for SWT
SWT seems more like an impatient solution to the problems of the first-party Java GUI toolkits than a real attempt to innovate GUI development in Java. Its low-level emphasis may be useful in certain cases, but I’d be hardpressed to see a case where SWT exposes something that Swing is unable to do.
Now, this last point is only more recently true. Swing did lack some expected features, such as system tray access, the capability to open a native browser or email client, and so forth. However, these things have since been remedied by their inclusion in AWT. Swing’s (and Java’s) performance problems have also diminished significantly, further reducing the need for native peers and low-level exposure.
Low-level exposure works against expectations in Java
On a fuzzier note, I find that SWT simply doesn’t feel like Java. This is aggravated by the fact that Java already has a GUI toolkit, so the standards have already been set. SWT’s design goals of exposing the meat of the interface you’re building on seems to work against the design goals of Java itself. SWT finds you having to do such heinous things as explictly disposing resources when you’re done with them.
Of course, explicitly disposing resources theoretically allows you to perform faster and cleaner than the garbage collector would otherwise allow. I am somehow reminded of standard versus automatic transmissions. I am also reminded of Pareto’s principle. If garbage collection is your bottleneck, you’re doing something wrong (or very odd).
Now, the problem I’m getting at here is not that SWT requires explicit resource disposal. Indeed, anything that’s touching the underlying native interface must concern themselves with disposal. The problem is that low-level access as a rule simply isn’t necessary in GUI toolkits.
It feels like someone missed programming GUIs in C/C++ and decided to write something in Java that mimics it as much as possible. They knew such a thing would be laughed out of existence, so they made Eclipse use it and named it something that makes it sound like it’s official.
There is no debate; use Swing
In all seriousness, SWT did solve some shortcomings with Swing in the past. But as Swing continues to improve, SWT’s reasons for existence deteriorate. SWT will not get you magnitudes of performance increase on a noticeable level. Nor will its low-level programming model afford you customization that you can’t do in Swing. I simply don’t believe there are valid, wide-scale arguments anymore for using it over Swing.
Convention, Configuration, and Customization
The Scenario, and its Hairy Implications
Imagine you’re writing a class for some framework. You expect a healthy amount of people to use it; it’s not disposable. Designing even a simple class for this case can be painful, usually because your design motivations conflict with one another. Let’s approach these from the viewpoints of various people:
You. You, being the developer, want to write a useful class. You’re hoping to write it fairly quickly, and you don’t want to get stuck correcting mistakes and additions to it
The Casual User. He needs the functionality your class provides, but he doesn’t want to waste alot of time trying to use it. The functionality this provides is tangential to what he’s working on. As a developer, this user is highly predictable in how he’s using your class, and you want to minimize time and code spent for this individual.
The One-Off Guy. He wants the functionality of your class, but will need to do some configuration to get it to work like he wants to. He’s not stretching the definition of your class very much, so ideally the effort required should be trivial and obvious.
The Academic. He sees potential in your class to perform something that you’re not likely to anticipate. He’ll probably want to subclass your class, or plug in a subclassed component that your class uses, to get what he wants.
These three individuals all stress different parts of the system. The casual user loves convention; he is, by definition, conventional. Catering to him will make your class seem monolithic. Favoring the one-off guy instead will stress the configuration of your class. His interests will involve indirect access of some small piece of your system.
These two individuals don’t usually clash. Adding configuration to help the one-off guy usually means providing a default for the casual. In many ways, they’re the same person: Slight variance in the design and defaults of the class will make casual users have to be one-off guys and vice versa. If you can be aware of this balance, you can tune your defaults to favor the majority.
Dealing with Academics
Academics are fringe-cases. They may be interested in pieces of your system that aren’t typically touched by other users. For example, they may not be interested in the conventional use of your system, but the workflow that your class provides. A classic example would be a socket stream: he may want to use the interface that the stream provides, but write it to use an entirely different medium, such as accessing a file locally.
The academic may seem like a trivial concern, since they’re heavily outnumbered by the first two. However, academics stress the design of your class directly. Adding file I/O directly to the stream class makes socket-specific functionality senseless. In the face of this, you have a few options:
- Do nothing. You may choose to rely on the intelligence of your users to ignore socket-specific functionality if they’re dealing with files. While easiest for the developer, it’s rude to make users have to guess how your class should work, especially when your interface isn’t consistent.
- Retrofit the Conventional Case. You may be able to get away with fitting the new functionality into existing methods. In this case, a file stream is “connecting” to a file like a socket stream would connect to a wayward socket. This makes the interface seem consistent, but it really isn’t. You’re still relying on the fact that users will use your class conventionally, and you’ve actually done them a disservice by hiding the true intentions of your class.
- Subclass! What the academic seems to want is a new class that uses the functionality of the parent while extending it to his own needs. Subclassing clearly indicates there is a specialization of functionality present, and both options are clear on their own. This seems like the safest solution, but it has hidden dangers in how the breakdown is performed. You can’t just subclass your socket stream, since doing so leaves you with the same consequences as the first two options (Which one you’re left with depends on details of your implementation). While you have a separate class that works with files, you’re still left with a ambiguous interface that people are stuck using.
Now, this is solvable too. If you remove socket-specific stuff and place it in its own subclass, and make the original stream abstract, it’s pretty obvious what you’re doing.
The Problems of Subclassing
The problem I see with this is proliferation. A FileStream and a SocketStream class may themselves be extended to provide specialized functionality. If they are, then the subclasser must decide which type of stream he wishes to specialize. If he tries to do the Right Thing and be as flexible as possible, he must subclass every permutation of the Stream class. So now you have Stream, FileStream, SocketStream, SpecializedFileStream, SpecializedSocketStream. You’re also forced to implement the full interface of Stream (Even if that interface just throws exceptions) since you’re masquerading as a stream.
Wrapping the streams through composition cleans up the hierarchy. You’d have instead your base Stream classes, and your specializer that takes a stream as an argument to its constructor. Composition is a clean alternative to subclassing that doesn’t muck up the class hierarchy. As a rule, flat class hierarchies are much better than deep ones. The problem here is that it’s difficult to justify not subclassing. After all, your Specializer class is essentially a stream. The only difference is that it’s not a stream like the original stream is.
Refactor? The problem isn’t solved by composition or inheritance because the original class is ill-defined and should be refactored. We organized the functionality of the stream into one class because it makes semantic sense to do so: We connect to the socket, send and receive data, then close it. File I/O involves opening a file, reading and writing data, then closing it. Our problems arise because we have two responsibilities, both potentially flexible, in the same class. When this happens, you’ll have this problem of class proliferation. Composition masks the problem, but inhibits the benefits of the class hierarchy.
What we should do is separate the stream class into its two natural components: The connection, and the encoding. We’ll steal a UNIX tradition and treat files and sockets as fundamentally the same thing. This probably isn’t that big a jump for anyone. The strength here is separating the management of the connection from the encoding of data. Instead of making a Stream class for every type of connection, we separate them both entirely into Encoders and Locations. A stream is a composition of these two. A file and a socket are both subclasses of Location, and a specializer and our original Stream class are both encoders. A stream is, for the most part, a convenience class that is given these two components and manages the events of both.
Why Good Decomposition Is Better
One could probably argue that this solution is overly complex. After all the work, we’ll have the Encoder, Location interfaces, and the Stream class. Two classes implement Location, File and Socket. On top of that, we’ll have our DefaultEncoder and SpecializedEncoder to solve the original problem. That’s five classes and 2 interfaces. That’s more than any other solution. However, hear me out: Classes that are decomposed well are better than fewer classes that are decomposed poorly. We found and separated the two responsibilities that were causing us problems earlier, and now we’re left with two hierarchys that are both very specific in their purpose. As a consequence, I’ll imagine they’re very easy to test. Problems with connection management are isolated inside the Location class, and problems during encoding are isolated within the other. Both classes are also extensible to add new methods of encoding or destinations to which to send.
Even further, there is no coupling between the two class hierarchies. While we’ve constructed them to use them together, there’s nothing keeping us from using these two hierarchies elsewhere. The fact that they are separate encourages this possbility. Sensible factory methods (presumably in Stream) will allow the conventional user to use our Stream uninhibited by the newfound flexibilty that we’ve provided him. The logical decomposition of these responsibilities allows the Academic to extend this class with ease. A set of preconstructed locations and encodings will allow the one-off user easy configuration of the stream.
Why OOP Matters
I should stress one final point: The goal of OOP is not code reuse. We benefit from code reuse when we do good OOP, but reusing code does not mean we are practicing good design. The real goal of OOP is to allow decomposition of responsibility in a fashion that closely resembles how we work and think. This contrasts with functional programming, where decomposition is done along the preference of the machine. The original design of the stream class resembles this – we hardcoded the semantics of the conventional use in the design of the class, instead of hardcoding the responsibility of each concept into the classes.
This probably isn’t very clear – a functional approach to decomposition would yield classes that mirror data structures. The Stream class is defined by how we’d use it, and its functions expose that conventional use. This works as long as we use the class conventionally. However, when we move beyond that, our class becomes a barrier to progress. It’s difficult to extend a class that exposes this kind of interface. A good responsibility-driven approach to decomposition will yield classes that allow extension and facilitate easy maintenance and testing.
ScrolledComponent
In writing the previous article on extending ScrollControlBase, I mentioned that the current implementation could be used as a generic scroller for any component. However, in keeping the article on topic, I didn’t really delve into how that would be done. I ended up writing it, so here’s a link to the complete source of ScrolledComponent. Continue reading this entry »
Extending ScrollControlBase: The Workflow
This post is the last page of a 4-post series on “Extending ScrollControlBase.” It describes the completed widget, along with providing a checklist on how to properly implement a component extending ScrollControlBase. Continue reading this entry »
Extending ScrollControlBase: Getting Scrolling to Work
This post is the third page of a 4-post series on “Extending ScrollControlBase.” It discusses how to use ScrollControlBase’s setScrollBarProperties method and how to implement its scrollHandler event listener. Continue reading this entry »
Extending ScrollControlBase: Displaying the Scrollbars
This post is the second of a 4-post series on “Extending ScrollControlBase.” It discusses how to override UIComponent’s validation methods in order to display the scrollbars provided with ScrollControlBase. Continue reading this entry »
Extending ScrollControlBase: Its Internals and Design Philosophy
This article is a 4-post series on extending ScrollControlBase. This post discusses how ScrollControlBase works internally, specifically how masks work, and what ScrollControlBase does, and doesn’t do, for subclasses. Continue reading this entry »