Just came across an excellent talk by Jason Fried (founder of BaseCamp") at TED. It’s a few years old, but not less pertinent.
The basic idea is that the office is a terrible place to actually get any work done.
Good stuff.
Just came across an excellent talk by Jason Fried (founder of BaseCamp") at TED. It’s a few years old, but not less pertinent.
The basic idea is that the office is a terrible place to actually get any work done.
Good stuff.
I had to use the shLegacy.js option since I’ve got tons of <PRE> tags all over this site using the older version. Not great bbecause the “Autoload” function for syntax brushes doesn’t seem to work in conjunction with shLegacy.js, but I suppose that’s to be expected.
If you notice any “unhighlighted” code, just let me know.
Fair warning; this post doesn’t have a lot to do with the Feng Shui of VB, or does it…. <g>
I’ve been recently playing quite a lot with Javascript, and various frameworks and libraries that really turn the browser into a client development platform in its own right.
To me, that combination just rings truer than the use of server-side html-generating frameworks like ASP.net, etc. But that’s philosophy for another discussion.
In this case, I’m using JQuery, JQueryMobile, and Knockout to build a mobile capable app that can run on various devices via single source, and hopefully be deployed via PhoneGap, but still look relatively native on each device.
If you’re not already aware of these libraries/frameworks (it can be difficult to tell exactly where each of these fits in the grand spectrum of browser client side development):
I wanted to bind a SELECT list to the equivalent of a .net ENUM in Javascript (which doesn’t technically have ENUMs, so I had to create a class to support something similar).
Here’s the KnockOut View Model:
1 2 3 4 | var userProfileViewModel = { timeToTrack : ko.observable(SecondsEnum.secs15), secondsEnum : ko.observableArray(SecondsEnum.toArray()) } |
The toArray function is just a convenience function to convert my ENUM class into a typical Javascript array that KnockOut can work with. Eventually my plan is to construct a KnockOut Custom Binding that would do all this automatically . But that’s for another post…
The HTML to actually view and bind to that model looks like this:
1 2 3 4 5 6 7 | < span data-bind = 'text: timeToTrack' ></ span > < select name = "selTimeToTrack" id = "selTimeToTrack" data-mini = "true" data-bind=" options: secondsEnum, optionsText: 'desc', optionsValue: 'value', value: timeToTrack"> </ select > |
Everything worked great, except for one nasty bit. When the displayed page first comes up, the select lists are empty. You can see the effect here.
Notice how the 15 that’s highlighted DOES populate properly (the value of the bound property is 15 in the view model), but the dropdown list value is empty.
Interestingly, if I click the dropdown, the resulting list does show the proper initial value (15 seconds):
After days of Google searching for answers, I finally got a few hours of free time and decided to just roll up my sleeves and start tracing through the KnockOut unminified debug version.
Eventually, I fell into this function (in KnockOut.2.1.0.js):
1 2 3 4 5 6 7 8 9 10 11 12 13 | function ensureDropdownSelectionIsConsistentWithModelValue(element, modelValue, preferModelValue) { if (preferModelValue) { if (modelValue !== ko.selectExtensions.readValue(element)) ko.selectExtensions.writeValue(element, modelValue); } // No matter which direction we're syncing in, we want the end result to be equality between dropdown value and model value. // If they aren't equal, either we prefer the dropdown value, or the model value couldn't be represented, so either way, // change the model value to match the dropdown. if (modelValue !== ko.selectExtensions.readValue(element)) ko.utils.triggerEvent(element, "change" ); }; |
Something didn’t quite look right about this.
If the first block of IF’s ends up true, the modelValue (the value of the property from view model) is going to be set as the initial value of the select DOM element, which is what we want.
BUT, if that happens, the next IF will ALWAYS be false (because the code just set the two value equal), so the “change” event will not be triggered in this case. The problem is, if it’s not triggered, the select DOM element won’t get updated to reflect that value that just got written.
My suspicion is that this was an attempt to circumvent having two lines of code to trigger then change element, but the logic just doesn’t seem to work.
So, I made a subtle change:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function ensureDropdownSelectionIsConsistentWithModelValue(element, modelValue, preferModelValue) { if (preferModelValue) { if (modelValue !== ko.selectExtensions.readValue(element)) ko.selectExtensions.writeValue(element, modelValue); // DWH added this to correct problem with the change not firing on initial population ko.utils.triggerEvent(element, "change" ); } else { // No matter which direction we're syncing in, we want the end result to be equality between dropdown value and model value. // If they aren't equal, either we prefer the dropdown value, or the model value couldn't be represented, so either way, // change the model value to match the dropdown. if (modelValue !== ko.selectExtensions.readValue(element)) ko.utils.triggerEvent(element, "change" ); } }; |
Here, you can see the “change” event is triggered in both situations (whether the ModelValue is preferred or not).
Once I made the change, I run the page and now I get the much more reasonable:
Notice how each dropdown list is initially populated with the current value from the view model.
Javascript (and dynamic languages in general) are a huge departure from static languages like VB ( and all the .net languages). Whether that’s a good thing or not is debatable, but the fact is, there is a tremendous amount of flexibility available because of the dynamic nature of Javascript. And frameworks like JQuery and KnockOut really bring some fantastically clean functionality to client-side browser development.
But, as with most code, these frameworks aren’t bug free. I’m not saying that this is definitively a bug in the KnockOut code, yet, though. I’ve posted to the KnockOut forums and am awaiting a reply.
In the meantime, though, this is a quick and easy fix to a vexing problem.
So if you’ve run into similar problems with KnockOut, it might be worth investigating.
We have a multiple computer household, but for some reason, my workstation seems to be the one everyone wants to use to check for directions, their email or whatnot. Unfortunately, as I research issues online, I often leave multiple tabs open as quick and dirty bookmarks till I’ve finished whatever work I was doing.
I happen to use the excellent TabMixPlus plugin for Firefox to manage this, and generally, it works great.
But, from time to time, I’ll sit down to continue work, open Firefox and all my tabs will be gone! Even LOCKED tabs! Ugh.
So a few days ago, I decided to try and track down why.
Turns out it’s pretty easy to accidentally do just that.
All it takes is a few simple (and easy to accidentally perform) steps:
At this point, if you reload Firefox, all your tabs will be gone!
It turns out that TabMixPlus, as good as it is, doesn’t quite handle this situation properly. If you close the window with all your wanted tabs FIRST, they will be saved properly. But then, when you close the second window with some other (not your) tabs, when that window closes, those tabs will wipe out the previously saved list!
Yikes!
Now, I can’t really call this a bug on Firefox’s or TabMixPlus’s side. It’s a pretty good example of an odd edge case.
Still, that’s not the way I want it to behave.
After some digging around, I’d turned up a number of suggestions, including changing the FireFox shortcut link, adding command line options like “about:Blank”, and others.
However, it turns out that the best solution was in TabMixPlus the whole time, just not quite as I would have thought.
On the LINKS tab of the TabMixPlus config options is the “Enable Single Window Mode.”
Just make sure that option is CHECKED.
If you do that, anytime a new, EMPTY Firefox window would have opened, instead, a new tab will open in the existing window.
This still means other users could close your tabs, etc, but they’d have to do it intentionally. It can’t happen by accident.
And… all your locked/protected tabs will stay in place, locked and protected. Those would have to intentionally be unlocked/unprotected to be closed out.
So far, I haven’t run into any issues as a result of enabling this mode, but, obviously YMMV.
NOTE: My take on this has changed slightly, as a result of some feedback. See the end of this post and the "Better Alternate Solution” for details.
I recently completed restoration of an old 1930’s era console radio into a fully modern touch-screen mp3-playing networked jukebox, and have been putting the finishing touches on the loaded software configuration for a few weeks.
Radio as we found it, stuffed at the back of a barn/storage area |
After the conversion, that touchscreen you see automatically folds and the top lowers back down to it’s original position when not in use |
There were 3 knobs on the original unit. The actual knobs were long gone, but the holes were still there and I wanted some old-school tactile controls for this thing, as well as the touchscreen.
A few ebay searches and junk drawers scavanges later, and I had 3 very serviceable knobs for the front. You can see them in the photo, the two large dial knobs and the one small brass knob on the center plate.
Now that that was taken care of, I had to determine
Believe it or not, the “what” has actually turned out to be harder than the “how”
Realistically, the what doesn’t much matter from a technical standpoint. Controlling volume, track playback etc is pretty trivial stuff with most software these days. So I’ll focus on the “how”.
I’d used a macro application called AutoHotKey on several occasions, and, while it’s programming language is quirky to put it mildly, it does get the job done, particularly when the job consists of converting one type of computer input into another.
In my case, each of those knobs it connected, via a typical encoder wheel, to the inputs of a standard 3 button optical mouse with middle wheel. Yes, you heard that right, there are three mice lurking in that cabinet (a forth if you count the real mouse that I use when working on it).
If you’ve ever hooked up 2 or more mice to a Windows PC, you know that they all control the single cursor. There’s no clear way to tell one mouse’s input from the other. Windows does that intentionally because it generally makes sense.
However, underneath the covers, there’s an API that exposes all input devices uniquely, meaning you CAN actually tell whether one mouse or another is being moved, clicked, scrolled or what not. It’s called the “RawInput” API, and it’s been there ever since Windows XP (and possibly even earlier, though I’m not sure of that).
Unfortunately, the RawInput functions are not the easiest functions to deal with in the world. Lots of C-style structure parsing and pointer juggling.
Fortunately though, for AutoHotKey users, a user named “theGood” posted AHKHID.ahk, an AutoHotKey script of functions to make working with HID devices (Human Interface Devices) at the RawInput level relatively straightforward.
I’d been able to craft up a script to detect which of my mice were been wheeled and clicked and react accordingly, so things were good.
Until I unplugged the mouse I used for working on the jukebox, and the keyboard.
When I booted it back up, boom, nothing worked right.
I had been using the function AHKHID_GetDevIndex, as in:
id := AHKHID_GetDevIndex(h)
in order to retrieve a device ID and I’d assumed that ID would consistently identify a device. And I’d assumed wrong. Unplugging my actual mouse and keyboard had caused the devices to get renumbered on reboot, a common issue with Windows applications.
A Nasty Solution
The AHKHID script includes a function called AHKHID_GetDevName() that retrieves a unique “device name”, but that “name” is huge, consisting of a number of segments of hex number, plus an additional entire GUID, something along these lines:
USB\VID_045E&PID_00DD&REV_0173&MI_00{745a17a0-74d3-11d0-b6fe-00a0c90f57da}
Obviously, this could be used for a key, but it’s a bit unwieldy.
So, to shorten the key name a bit and make it easier to work with, I came up with AHKHID_GetDevKey()
AHKHID_GetDevKey(i, IsHandle = false) { ;generate a unique name from the DevName (which is huge, and usually includes a useless GUID) devname := AHKHID_GetDevName(i, IsHandle) StringReplace, devname, devname, Hid#Vid_, , All StringReplace, devname, devname, USBVid_, , All StringReplace, devname, devname, KODAK, , All StringReplace, devname, devname, &Pid_, , All StringReplace, devname, devname, &MI_, , All StringReplace, devname, devname, #7&, , All StringReplace, devname, devname, #, , All StringReplace, devname, devname, &, , All StringReplace, devname, devname, \, , All StringReplace, devname, devname, ?, , All p := Instr(devname, "{") if p > 0 { p := p - 1 StringLeft, devname, devname, p } ;remove last 5 0's if that's what's there if SubStr(devname, -4) = "00000" { p := StrLen(devname) - 5 StringLeft, devname, devname, p } StringUpper, devname, devname Return devname }
The idea here is to essentially strip out all the characters that wouldn’t help to make the string any more unique anyway. It’s a tad brute-force, but it’s easy to understand and it works.
So that insane name could end up a relatively benign:
045E00DD017300
Which is a perfectly reasonable unique key in my book.
After I’d put this together and gotten it in use, I realized a potentially even better solution: just calculate a CRC-32 for the full Device Name. It turns out another user has already built a CRC-32 algorithm in AHK script, so doing so would be trivial. Granted, you might still get collisions with a CRC, but it’s reasonably unlikely.
Well, after posting this, I got a comment on the AutoHotKey forums that, essentially, was pointing out that the “VID_” and “PID_” strings that my routine removes, actually can have an impact on the uniqueness of the name. The poster also indicated that the GUID at the end of the name also aided in the uniqueness.
While neither of those two claims held true in my specific case (on the 3 machines I tested on), I don’t doubt them in the list.
Which got me to thinking if there’s a better solution.
And there is. Our old friend, the hash function.
In reality, the CRC-32 approach that I suggest above can certainly be considered a form of hash function, and a pretty good one at that.
But, if you’re really gunning for just about ironclad uniqueness, at 64 or 128 bit hash is what you need, and there’s a couple of implementations that already exist for AutoHotKey.
Grab one of those, run it against the GetDevName value, and convert to a string value and you have a relatively short and manageable, unique and consistent HID device ID.
I love Visual Basic, and VB.net in particular, but AutoHotKey makes many tasks concerned with translating one type of input into another (converted joystick input to mouse input, or keyboard keystrokes, for instance) so easy, it just often doesn’t make sense to build a dedicated app for the purpose.
It’s definitely an application to key handy in your toolbox.
NOTE: I originally did this post back in 2007. I’ve updated it a few times since then with new fonts. I added Lucida Sans Typewriter at the request of a comment by Row Sham Bow. Also new is Inconsolata, DroidSansMono, Liberation Mono, and Monofur.
And for 2012, at the suggestion of Chris Nicholls, Borte.fon is also new.
Thanks to everyone who’s pointed me to new fonts to try!
I’ve tried out the newer fonts for varying lengths of time, but, at least so far, I still end up back with Consolas.
But if you have a favorite font you’d like mentioned, please let me know!
I’ve noticed something a bit peculiar about fonts and programmers.
There’s the camp that’s thoroughly researched the topic, experimented with piles of fonts and ended up selecting one that they can be quite vocal about.
And then there’s the camp there never changes the default that came setup in the IDE they installed.
I guess I come from the former. Courier? No way. Arial? Ack! Programming in a variable width font just seems, so, ewww. I was a good, old fashioned FixedSys man for ages. It was decent, had a nice fixed width, slightly too bold, but I could live with it, came with every copy of windows and was pretty legible across all the common characters. But about a year ago, I embarked on a quest to find a better font.
Many, many downloads later, I believe I’ve ended up with the best of the options out there. The freely available ones, anyway.
I’ve taken screen snapshots of all fonts at approximately 14pt size. I used 14pt because it’s the most comfortable size for me.
I’ve also included a snapshots of the better fonts at different sizes, so you can get a feel for how they scale.
I’m not a big fan of the bitmap fonts (the FON files). They don’t scale well except where they’ve been defined at multiple sizes (not really scaling), and don’t believe they take advantage of ClearType, certainly not very well. Still, there are some interesting bitmap fonts out there.
Most of the good ones are intended to be used as very small fonts. If you have eagle vision, some of them might work quite well. But my eyes would jump screaming from my their sockets if I tried to program in some of these all day.
![]() |
10×20-iso8859-1.fon | Not bad but it doesn’t scale. Some elements bleed too much. |
![]() |
9×15-iso8859-1.fon | Too thin for me |
![]() |
9×18-iso8859-1.fon | Also too thin |
![]() |
BSUKermit.FON | Base size is too small. Doesn’t scale |
![]() |
Borte.FON | A bit small and thin for me, but otherwise looks very clear. Doesn’t scale |
![]() |
Dina.fon | Base size is too small. Doesn’t scale |
![]() |
DOSLIKE.FON | Not bad, but it doesn’t scale. Has a few too many serifs for my taste. |
![]() |
GwdTE_437.fon | Base size is too small. Doesn’t scale |
![]() |
Hackd.ttf | My own custom version of Hack, ligatures, slashed zero, nice percent, Powerline glyphs |
![]() |
Hyperlt.fon | Actually called HyperFont LT |
![]() |
IBMPC.fon | 10pt. Yikes. Quite small. |
![]() |
IBMPC.fon | 14pt. This is what happens when bad fonts scale. |
![]() |
PROFONT.FON | 12pt. Too small, and the letters bleed together at this size. |
![]() |
PROFONT.FON | 14pt. Way too thick at this size, plus kerning is wacked. |
![]() |
Raize.fon | 10pt. Not too bad, but too small for me. |
![]() |
Raize.fon | 14pt. There’s just something about the letter shapes at this size that don’t feel right. |
![]() |
Sysmono.fon | As close to 14pt as I can get with this font. |
I prefer the true type fonts. They scale well all the way down to about 7 points, but they can take advantage of ClearType, and a few are even specially designed to make optimal use of it.
![]() |
Arial.ttf | 14pt. Just for reference, a variable width font just doesn’t work for me and programming. |
![]() |
Aerial Mono.ttf | 14pt. Pretty good. Seems a bit too bold. |
![]() |
Andale Mono.ttf | 14pt. Quite good. This one was in the running for a while. |
![]() |
AnonymousRegular.ttf | 14pt. Yikes, serifs and programming just don’t mix. |
![]() |
AnonymousRegular.ttf | 11pt. Smaller size, same serif problems. I do like the zero, however. |
![]() |
Bitstream Vera Sans Mono.ttf | 14pt. Excellent font. Maybe just a tad too bold, but this one was also in the running. |
![]() |
Consolas.ttf | 14pt. Excellent all around. |
![]() |
Consolas.ttf | 10pt. For reference. Scales well. |
![]() |
DroidSansMono.ttf | 12pt. Very good font. Google makes it available free here. Only downside is the zero is not slashed. |
![]() |
Everson Mono Unicode.ttf | 14pt. Very good, but the letters seem to be a bit thinner than numbers and symbols, which looks odd to me. |
![]() |
Fixedsys.ttf | 14pt, note this is the TTF version of the FixedSys font, not the bitmapped version. It’s too heavy for me. |
![]() |
HyperFont.ttf | 14pt. Not bad but similar problems to Everson Mono. |
![]() |
Inconsolata.ttf | 12pt. A nice font but I don’t really care much for what anti-aliasing does to it. It seems fuzzier than necessary and some letters end up with a nib below them, which I don’t care for. |
![]() |
Larabiefont Bold.ttf | 14pt. Wow. Those zeros. oh, wow. Screams l33t! |
![]() |
Letter Gothic Line.ttf | 14pt. This one’s not too bad, but check out the lower case m’s. Funky. |
![]() |
LiberationMono.ttf | 12pt. A very good font. Available here. Very much a contender for a top spot, but it uses a dotted zero instead of a slash. |
![]() |
Lucida Sans Typewriter | 12pt. Not bad, though it seems a little thin to stare at all day. |
![]() |
Monaco.ttf | 14pt. Excellent font. Originally from the Mac. the parens are a little odd, but this was definitely in the running. |
![]() |
Monofur.ttf | 12pt. Good but a little odd to program in all day long. The dotted ‘zero’ doesn’t feel right to me, but I do like the symbols. |
![]() |
PixelCarnageMonoTT.ttf | 14pt. Hmm, seems small for 14pt. Decent, but not right for me. |
![]() |
ProFontWindows.ttf | 14pt. The “u”, “n” and “c” threw me. This is just a little too “programming in the FUTURE.. FUTURE.. FUTURE.. FUTURE”. |
![]() |
ProggyCleanTT.ttf | Intended to be used small. Ouch, my eyes! |
![]() |
Reader Sans Roman.ttf | 14pt. Funky horizontals. |
![]() |
Terminal.ttf | 12pt. Decent, available, a bit thick in general. |
![]() |
Terminal.ttf | 8pt. Fine if you don’t mind going blind at 30. |
![]() |
Ti92Pluspc.ttf | 14pt. Not bad but a little tall for my taste. Any shorter, though, and it gets too thin. |
![]() |
Topaz-8.ttf | 14pt. Ugh. Someone actually recommends this? |
I found all these fonts through various sites. Googling the font file name should get you where you want to be. I’m not sure as to posting the fonts for download here, so I’m erring on the side of safety. I apologize in advance for making you hunt them down.
I chose Consolas in the end, for several reasons:
I’m sure there’s a lot more possibilities out there, but I didn’t see them before I quit looking.
If you have a favorite font, let me know. If I can download it, and it’s a serious programmer font, I’ll add a sample.
If you tell me you program in Comic Sans, somewhere, out there, a daemon will abend.
Over the past few weeks, I’ve been working on a little screensaver side project to run in an old radio cabinet that I’ve converted into a touchscreen jukebox. Details of that project will be coming, but for now, I thought I’d share some interesting software bits I’ve discovered.
The screensaver that I’ve working on is intended to:
1) Recognize a Media Player that’s running on the computer when the screensaver loads up.
2) Connect to and monitor that media player for any changes in what’s it playing. Particularly, I want to know Artist name, album name and track name.
3) Hit a few search engines for some images related to what’s playing and display them in a nice “Ken Burns Pan and Zoom” style animation.
Well, long story short, WPF made short work of the image animations, and connecting to two of my favorite media players, J River Media Center and Album Player, was pretty trivial.
But, within just a few days of posting the first version, someone asked if it worked, or could work, with Windows Media Player (and, as it turns out, Windows Media Center, which is just a nicer shell over WMP).
My first thought was, sure! All I have to do it be able to see what’s currently playing and when it changes. Shouldn’t be too tough, right?
Well, after quite of bit of digging, it turns out that Windows Media Player (WMP), is far more gracious about hosting “plugins” that it is about being remotely attached to. There are several plugins available for WMP that write out info on the current playing media to an XML file, or the registry, or a web service, or whatever. But that requires "my application’s user” to install some other application to make things work. Not cool. At least, not for me.
Plan two. Most MS Office apps register themselves with the Running Object Table (The ROT). Other programs can query the ROT and retrieve objects from it that they’re interested in connected to. You often see the VB GetObject() function used for this purpose.
But WMP doesn’t register itself with the ROT, so that’s not an option.
On to Plan C.
However, as luck would have it, MS did do something about this type of situation. They call it “Media Player Remoting”. However, it’s just about the least documented concept I’ve come across yet. There’s just very little info about exactly how to set up this “remoting” or what it’s capable of.
Eventually,though, I did come across mention of a demo project written by Jonathan Dibble, of Microsoft no less, that illustrates the technique in C#. There’s a thread here that contains links to the original code, though that page appears to be in Japanese.
Looking further, I found several variations of Dibble’s project, some vastly more involved and complex than others.
I grabbed the simpler version and started hacking!
Converting Mr. Dibble’s code was fairly straightforward. He did a pretty fair job in commenting it and breaking things down nicely. As usual, one of my favorite Web Resources, DeveloperFusion’s CodeConverter, got a workout, and did a fine job on most of the conversion gruntwork.
But when the dust cleared, it didn’t work.
After a lot of stepping with the debugger, it turns out that Jonathan’s handling of IOleClientSite.GetContainer isn’t quite right. His original code threw a “Not implemented” exception that would cause a crash for me every single time.
The function itself isn’t particularly useful for what I needed to do, and after reading up on the documentation, I felt certain that there’s really wasn’t anything that “needed” to be done in that function. But, it did get called by WMP, and something other than throwing an exception had to be done there.
Then, I realized that a number of other OLE-centric functions that Jonathan had implemented had a return value of HRESULT, and simply returned a E_NOTIMPL value.
So, I changed up the definition of GetContainer and had it returning an E_NOTIMPL, and presto! It works!
Since Jonathan’s demo project appears to be at least 4 years old, I’m not sure whether I may have indeed worked that way at one point, or was a bug, or, quite possibly, was something I didn’t get quite right in the conversion in the first place. Regardless, this version works, so bob’s your uncle.
For anyone not interested in the details, I’ll dive right in to how you actually use this technique.
First off, you’ll need to add a reference to WMP.DLL. This file should be in your Windows\system32 folder if a Windows Media Player is installed. Once added, you’ll have a WMPLib reference in your References tab:
Next, copy the WMPRemote.vb file into your own project.
Finally, though this isn’t strictly necessary, you may want to alter your project’s AssemblyInfo.vb file and set the CLSAttribute to false.
1 2 3 4 5 6 7 8 9 10 | .... < Assembly : AssemblyCopyright( "blah" )> < Assembly : AssemblyTrademark( "blah" )> '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 'THE BELOW LINE YOU MIGHT ADD OR CHANGE TO "FALSE" < Assembly : CLSCompliant( False )> 'The following GUID is for the ID of the typelib if this project is exposed to COM < Assembly : Guid( "12333333-33454-1233-1234-123451234512" )> ..... |
The main class to work with is WMPRemote. It has two static properties; IsWindowMediaPlayerLoaded, and ActivePlayer.
These are static properties, so you access them using the WMPRemote class, like so:
1 2 3 | If WMPRemote.IsWindowsMediaPlayerLoaded Then Dim Player = WMPRemote.ActivePlayer End If |
At that point, if WMP is loaded, you’ll have a reference to the full WMPlib.WindowsMediaPlayer object in the Player variable.
From there, you can do whatever you need to.
Query properties:
1 | Debug.print Player.playState |
Attach to events:
1 2 | AddHandler Player.CurrentItemChange, AddressOf CurrentItemChange AddHandler Player.PlayStateChange, AddressOf PlayStateChange |
Or whatever else is necessary.
Since this class is the main access point, I’ll start here. There are only 2 static functions defined here:
This function simply uses the .net Processes object to query for any processes named WMPlayer. If there are any, it returns TRUE, if not, FALSE. Obviously, it could be wrong, but that’s not terribly likely.
If things have already been initialized, this function just returns whatever it’s already retrieved for the Active WindowsMediaPlayer object.
If not, it checks if WMP appears to be loaded and, if so, creates and shows an instance of the internal frmWMPRemote form. During the load of this form, it’s immediately hidden so the user will never see it.
The only purpose of frmWMPRemote, is to host the WindowsMediaPlayer ActiveX Control. This all happens during the form Load event:
1 2 3 4 5 6 | Me .Opacity = 0 Me .ShowInTaskbar = False _InternalPlayer = New WMPRemoteAx _InternalPlayer.Dock = System.Windows.Forms.DockStyle.Fill Me .Controls.Add(_InternalPlayer) Me .Hide() |
Note that it actually is creating an instance of the WMPRemoteAx control, and then siting it on the form.
This class is based on the AxHost control, and is what allows an ActiveX COM-based control to exist on a .net WinForms form.
Once created as actually sited on a control (or WinForms Form, in this case), the AttachInterfaces method is called by the .net runtime to connect this host up with whatever COM ActiveX Control it will be hosting. I’ve told this control to host the WindowsMediaPlayer ActiveX control by setting the GUID in the constructor:
1 | MyBase . New ( "6bf52a52-394a-11d3-b153-00c04f79faa6" ) |
AttachInterfaces connects up the ActiveX control with a clientsite as necessary, but more importantly, it exposes the internal OCX instance by casting it as an instance of WindowsMediaPlayer:
1 2 3 4 5 6 | Dim oleObject As IOleObject = TryCast( Me .GetOcx(), IOleObject) Dim a = TryCast( Me , IOleClientSite) oleObject.SetClientSite(a) _RemotePlayer = DirectCast ( Me .GetOcx(), WMPLib.WindowsMediaPlayer) |
At this point, COM calls several other interfaces that have been implemented by WMPRemoteAx, including:
Most of the methods on these interfaces need not actually do anything. They are required for more sophisticated integrations. However, IOleServiceProvider_QueryService does have a very specific purpose.
Remember that this entire situation is made possible by something WMP calls “Remoting”. Turns out, this callback method is how our control communicates to WMP that we are, in fact, setting up a remoting situation.
1 2 3 4 | If riid = New Guid( "cbb92747-741f-44fe-ab5b-f1a48f3b2a59" ) Then Dim iwmp As IWMPRemoteMediaServices = New RemoteHostInfo() Return Marshal.GetComInterfaceForObject(iwmp, GetType (IWMPRemoteMediaServices)) End If |
When WMP calls this function with the given riid as above, our control has to respond by returning an object of type IWMPRemoteMediaServices (in this case implemented by the RemoteHostInfo object in the project). That object has a few properties that WMP queries for some basic information, but really, the fact that our control (WMPRemoteAx) has responded by returning an IWMPRemoteMediaServices is the main thing that sets up the remoting connection.
The rest of WMPRemote.vb is made up of several COM Enums and interfaces that are necessary to bring all this together, but that aren’t readily defined in VB.net. None of that code should normally be altered in any way because it’s actually defining interfaces and values that have already been defined in COM, but that we need defined for use in a .net application. For instance:
1 2 | <ComImport(), ComVisible( True ), Guid( "00000118-0000-0000-C000-000000000046" ), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _ Public Interface IOleClientSite |
This is the start of the definition of the IOleClientSite interface. That interface is a “well defined” interface with a specific GUID (the value of the GUID attribute), and a specific interface type. Changing our .net definition of that interface would likely break everything.
Right here. This particular version is in VS2010 with .net 4.0 framework, but I don’t imagine there’d be any problems converting back to VS2008, or possibly even VS2005. I suspect you will need at least .net framework 2.0 though, but I haven’t tested this.
Also, check out a few of my other projects in the Code Garage.
And finally, let me know what you think!
These are two incredibly handy utilities to have around. I’ve used them for years.
KeePass is a task tray utility for keeping track of and auto pasting usernames and passwords into forms. Works with local applications as well as web forms. The password database itself is highly secure.
PhraseExpress is an “autotype” tool that works across multiple applications, and can even do limited automatic correction of misspelling in any program.
However, when I updated to KeePass 2.18 recently, password pasting into WebForms (Specifically FireFox in my case) stopped working.
Well, sort of.
The username would typically paste in, followed immediately by the password. Even though KeePass was configured to send a TAB key between them and ENTER after the password, those keys weren’t getting sent, so the password paste didn’t work right.
Long story short, something changed in Keepass that suddenly makes the “Route Enter and Tab Key Through PhraseExpress” option of PhraseExpress very important.
I originally had this setting CHECKED, but that now appears to conflict with Keepass.
Unchecking the setting seems to correct the issue and Keepass works fine now.
I’m not sure what, if any, negative effect there is from unchecking this option though, when using PhraseExpress normally.
I ran into a strange problem today. I had brought up a new Windows XP machine, wirelessly connected to my network. It has SP3 installed, could see the internet, and could see other machines (including Windows 7 machines) on my network.
But, my Windows 7 workstation could not see the XP machine.
By “see”, I mean:
However, I had another XP machine that my Win7 workstation could see!
After a few google searches, I came across a post that mentioned changing a few Win7 networking settings.
In particular:
Open up Advanced Sharing Settings
And alter a few settings:
Notice that
I then logged out and logged back in (no need to turn off).
Presto! I can see both of my XP machines!
Ok, so which settings actually did the trick?
Long story short, I ended up setting ALL these settings back to their original values, rebooting between each, and even using IPCONFIG /flushdns, but I can still see my XP machines just fine.
Ugh.
In the end, I’m guessing switching those settings allowed “something” about the XP machine to register itself on my Win7 machine, after which, I could switch the settings back, but everything will still be OK.
But that’s just a guess at this point.
OK, It’s been a few weeks, so this is likely not “new” news, but still.
Looks like MS isn’t backing off on VB one bit, and that’s a good thing. Heck, as I understand it, there’s even a project in skunkworks build VB in VB! I’ve always said the mark of a complete language is when it’s actually written in itself.
But I digress. Back to VB 11. The VB Team website has a nice rundown of some of the high points of the release here.
To summarize:
What’s missing
It’s good to see the language getting very close to parity with C#, and the news of a forthcoming VB.net coded compiler is even more exciting.