|
|
|
|
| |
| | Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|
| 31 | 1 | 2 | 3 | 4 | 5 | 6 | | 7 | 8 | 9 | 10 | 11 | 12 | 13 | | 14 | 15 | 16 | 17 | 18 | 19 | 20 | | 21 | 22 | 23 | 24 | 25 | 26 | 27 | | 28 | 1 | 2 | 3 | 4 | 5 | 6 | | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
|
|
|
|
|
|
|
.NET Flea Market
|
|
|
|
Visual Studio.NET allows you to set a property that makes your application a single-instance application, where double-clicking the icon while the app is running simply brings the already-running app to the foreground. But what if you wanted the option of having multiple instances, say for power users? Well, you could start by creating an application setting, so you have My.Settings.AllowMultipleInstances to work with. But now where do you set it? Visual Studio allows you to patch in to application events. Click the View Application Events button in the application properties page and a new file is created: ApplicationEvents.vb, which is a partial class extending the MyApplication class. We can see that there are some events we can hook into. The Startup event appears to be the first event, so we put our code in there: Me.IsSingleInstance = Not My.Settings.AllowMultipleInstances And when we run the app, we see that it doesn’t make any difference. Apparently we are setting the property too late. Ok then, we need to move up the ladder. There is a file buried in the My Project section called Application.Designer.vb. There is a line in the constructor there that reads Me.IsSingleInstance=True (or false, depending on how you have it set). So we can set that value to the AllowMultipleInstances setting. And when we run the app this time, we end up with a stack overflow error. Why? As it turns out, while you are in the constructor of the MyApplication class, you are creating an instance of the MySettings class, which wants an instance of MyApplication, which isn’t done being constructed yet. So they go back and forth trying to get instances of each other and cause a stack overflow. Ok, the constructor is off-limits, then. Besides, that file is auto-generated and you’d lose that change the next time you made any change through the Visual Studio property page. Well, crap. The provided events are too late and the constructor is too early. There’s no other entry points. Not true! This is one of those things that VB programmers can do that C# programmers can’t. And of course C# snobs would just say “that’s poor coding”, but they’re just jealous. You can shadow the Run method in the WindowsFormsApplicationBase class, which is where the logic is that handles single/multiple instances. So in the ApplicationEvents.vb file, where you can extend the MyApplication class, add: Public Shadows Sub Run(ByVal commandLine() As String) Me.IsSingleInstance = Not My.Settings.AllowMultipleInstances MyBase.Run(commandLine) End Sub And now you can switch between single and multiple instance application settings. All we did was inject an extra line of code in the Run method, which is after the constructor but before the Startup event. Wow, I’ve been blogging for over 2 years?
|
|
|
|
|
|
|
|
|
No, this post isn’t a rant on consumerism and lack of true holiday spirit. I have another blog for that. This is my programming blog. I was thinking hard about what I could post to close out the year. It doesn’t seem much has been happening. But I recently moved from MS Money to Quicken and some of the changes between the two are a little jarring. The biggest difference is in the register, where the running balance only calculates the displayed transactions. So if you filter the view by date or status, the running balance has no useful meaning. I find this flaw infuriating, as do other former Money users, but Quicken users have dealt with it from the introduction of register filters. So, me being the “oh, it can’t be that hard” kind of person, I decided to test the waters and see how difficult it would be to re-write MS Money in .NET. Aside from a lot of the financial rules and calculations used in the investments section, I’m not discouraged yet. Last night I went through the screens in Money and whipped up some basic object diagrams.   And then I started working on some conceptual programming ideas to make sure I wouldn’t hit a wall. The first thing I wanted to make sure I could handle was a running balance in the register. It must be pretty difficult if Quicken can’t do it. I thought about all the different ways a register would be modified and the effect of those changes. But there was one difference between Quicken and Money that pointed me in the right direction. When you create a new account in Quicken, your opening balance is the first transaction for the account. In Money, your opening balance is stored in the account and there are no transactions. This said to me: Quicken cannot calculate a running balance without having that first transaction available. However, Money can. What is Money doing differently? I don’t know the exact answer to that question, but the solution I came up with was to create a field in the transaction (tx) table that stored the difference, or delta, from the opening balance. That way, I could take any single transaction or group of transactions, and get the running balance by adding the delta to the opening balance. I wouldn’t want to store the actual balance in there because if the opening balance was ever changed, I’d have to update EVERY transaction. So in simplicity, we have two tables that look like: CREATE TABLE Account(
ID int IDENTITY(1,1) NOT NULL,
Name nvarchar(50) NOT NULL,
OpeningBalance money NOT NULL
) ON PRIMARY
CREATE TABLE AccountTX(
AccountID int NOT NULL,
TXAmount money NOT NULL,
AccountDelta money NULL,
TXID uniqueidentifier NOT NULL,
TXDate datetime NOT NULL
) ON PRIMARY
And of course the tables have appropriate defaults. A simple query with populated tables would be
select a.name,tx.txid,tx.txdate,tx.txamount,
a.openingbalance+tx.accountdelta RunningBalance
from accounttx tx
join account a on a.id=tx.accountid
order by tx.txdate
But we want the database to manage the AccountDelta field itself. We can’t be expected to make all the updates whenever a user changes the TX amount or moves the date forward or back. So we’ll handle this with triggers.
First, the insert. We’ll assume that a new transaction is always entered at the current date and time (which isn’t realistic, but just play along) and that only one row is inserted at a time. The trigger would be:
CREATE TRIGGER dbo.Add_AccountTX
ON dbo.AccountTX
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON
declare @LastDelta as money
-- Get the previous delta from the last transaction
select top 1 @LastDelta=AccountDelta
from AccountTX
where TXDate<getdate()
order by TXDate desc
-- add the new row with the adjusted delta
insert AccountTX(AccountID,TXAmount,AccountDelta)
select AccountID,TXAmount,isnull(@LastDelta,0)+TXAmount AccountDelta
from inserted
END
We’re using an INSTEAD OF trigger so we can completely control the insert. And it’s pretty simple - we get the last delta and add or TX to it when we insert the row. The DELETE trigger is pretty easy as well - we just deduct the TX amount from any future transactions.
However, when updating, the user could be changing the TX amount, which affects all future deltas, or they could change the TX date, which will change the deltas from the earlier of the old TX date or new TX date.
ALTER TRIGGER [dbo].[Update_AccountTX]
ON [dbo].[AccountTX]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON
if update(TXDate)
begin
declare @txAmount money,@txDate datetime,@TXID uniqueidentifier,@LastDelta money
declare priorUpdates cursor for
select old.TXAmount,old.TXDate
from inserted new
join deleted old on old.txid=new.txid
where old.txdate<new.txdate
order by new.TXDate asc
open priorUpdates
fetch next from priorUpdates into @txAmount,@txDate
while @@fetch_status=0
begin
-- Update future TX to reflect removal of current TX
update AccountTX set AccountDelta=AccountDelta-@txAmount
where TXDate>=@txDate
fetch next from priorUpdates into @txAmount,@txDate
end
close priorUpdates
deallocate priorUpdates
declare updates cursor for
select new.TXAmount,new.TXDate,new.TXID
from inserted new
order by new.TXDate asc
open updates
fetch next from updates into @txAmount,@txDate,@TXID
while @@fetch_status=0
begin
-- get the previous delta
select top 1 @LastDelta=AccountDelta
from AccountTX
where TXDate<@txDate
order by TXDate desc
-- Update the delta for the row moved
update AccountTX set AccountDelta=isnull(@LastDelta,0)+@txAmount
where TXID=@TXID
-- Update all future transactions
update AccountTX set AccountDelta=AccountDelta+@txAmount
where TXDate>@txDate
fetch next from updates into @txAmount,@txDate,@TXID
end
close updates
deallocate updates
end
else if update(TXAmount)
begin
declare @diff money,@minDate datetime
declare updates cursor for
select new.TXAmount-old.TXAmount Diff,new.TXDate
from inserted new
join deleted old on old.txID=new.tXID
order by new.TXDate asc
open updates
fetch next from updates into @diff,@minDate
while @@fetch_status=0
begin
-- Update all future transaction with the difference
update AccountTX set AccountDelta=AccountDelta+@diff
where TXDate>=@minDate
fetch next from updates into @diff,@minDate
end
close updates
deallocate updates
end
END
Yeah, it’s bulky. There’s two sections depending on whether the amount changed or the date changed. The date is difficult because you have to handle TX’s moving forward and backward, and it’s essentially doing an insert and a delete. The change to the amount is simple, it’s only twist is having to determine how much the difference is to apply to future deltas.
But, the point of the exercise was to see if I could do it at all, and I feel comfortable with what I’ve got. The bulk of the code will be in display of the transactions and the investment logic. If Money’s UI isn’t already written in WPF, then that would be an excellent fit for the design. We’ll go slowly on this. I need to make sure that I have some other concepts worked out as well.
But that will be discussed in the coming year. Good riddance, 2009.
|
|
|
|
|
|
|
|
|
I recently got myself a Zune HD. I do like it a lot. But now my video files just don’t have the quality to impress anyone when I want to show it off. So I decided to re-rip some of my videos in a higher definition. I soon remembered how much I hate video conversion. My goal was to get the highest quality file onto the Zune HD without having the Zune software transcode it and to do it using freeware tools. Whenever you choose to go the freeware route, you’re going to be sacrificing convenience. The big hammer in my toolbox is the free Windows Media Encoder (WME). This will encode to WMV, which is a format the Zune loves to eat. So everything I’m doing is revolving around this tool. However, now I need a bunch of additional tools to work around its shortcomings. WME will only work with a single file at a time, so DVDs with multiple VOB files can’t be processed directly. So, I used DVDShrink to rip the main feature from a DVD to a single VOB file (this is set in the options). At the same time, I used DVD43 to remove the encryption. DVD43 only works under 32-bit systems, so I run DVD43 and DVDShrink in a Windows XP virtual machine hosted with Sun VirtualBox. So far, we have 4 programs involved. The VOB is now ripped and put into WME. Now is an excellent time to discover that WME doesn’t read AC3 audio from VOBs. Now we need a program to demux the audio into its own file and let WME mux them back together. This can be done pretty easily with BeSweet, but you also need to download the VOBInput.dll additional file. Now 5 programs. Now WME can read the video from the VOB, the audio from the WAV and put them together. But on the very first DVD I tried – Talking Heads, Stop Making Sense – the encode would stop after about 40 minutes. Looking at the details a little closer, the VOB was reported to be 40 minutes long, but the WAV was 1:28 long. When playing the VOB, the time would reset to zero after the 40 minute mark. This, I’ve learned, is a timecode break. Now we need a tool to fix this. The timecode break can be fixed using a tool called StreamClip. And it requires that Quicktime Alternative 1.81 be installed. These are going on the virtual machine so as not to pollute my main system. Unfortunately, StreamClip seems to be designed for Apple users (the cancel button is on the left of the windows), so all the audio export features are incompatible with WME, so BeSweet has to be kept. So in summary, this is what I needed to take a DVD and put it on the Zune HD, in order of execution: - Sun VirtualBox – Host for a 32-bit Windows install
- DVD43 – Decrypts DVDs. Only runs in 32-bit. Run in virtual machine.
- DVDShrink – Rips DVD to single VOB file for processing. Run in virtual machine
- Quicktime Alternative – Needed for StreamClip. Run in virtual machine
- StreamClip – Fixes timecode breaks in VOB file (not always needed). Run in virtual machine
- BeSweet – Extracts audio from VOB to WAV
- Windows Media Encoder – Creates the WMV file from the VOB and WAV files.
That’s a lot of programs and a lot of work, but hey, it’s free! Once I have a workable routine, I’ll try to post something on it. The BeSweet and WME steps aren’t just a simple point-and-click procedure.
|
|
|
|
|
|
|
|
|
Remember that little idea that Office had a while ago that would hide infrequently-used menu items? Wasn’t that a great idea? For me, it was the very first thing I turned off after installing Office. But I do understand what they were going after. When applications do so much, every user is probably just using a subset of the whole application’s features. The application that I’m writing is kind of getting like that. A few versions ago, I created a toolbar on the side that was planned to be context-sensitive, so it would show actions based on what data was shown and available - kind of how Microsoft is now doing with the task pane. Eventually, I may create or convert the toolbar to a task pane. But as the application was growing, I had the same thought the Office designers had: each user probably only cares about 5 or 6 menu items at a time and those items should be as readily available as possible. So instead of making personalized menus, I decided to create a Favorites toolbar. This is similar to Microsoft programs where you can add toolbars and put menu items on them. Because the application is in flux and because I am lazy, I didn’t want to go through the effort of creating a “Customize Toolbar” dialog. I also didn’t want to have an extra dialog for “Add To Favorites”. So what I did was allow menu items to be dragged onto the toolbar. The proof-of-concept started as most do, just to see how it would work. I got it going in under 150 lines of code, even less considering whitespace and definitions and all. To quickly summarize the technique, I started by putting a toolbar container on the form, adding a toolstrip to hold the favorites, and adding a menu to hold the draggable items. Then I added the code to allow the dragging of the menu items: Private Sub Menu_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) _
Handles mnuFirst.MouseMove, mnuSecond.MouseMove, mnuThird.MouseMove, mnuFourth.MouseMove, _
mnu2ndLevel1.MouseMove, mnu2ndLevel2.MouseMove, mnu2ndLevel3.MouseMove
Dim item As ToolStripMenuItem
If e.Button = Windows.Forms.MouseButtons.Left Then
item = CType(sender, ToolStripMenuItem)
item.DoDragDrop(item, DragDropEffects.Copy)
End If
End Sub
Then the code to drop the items (the toolstrip needs to have AllowDrop set to True):
Private Sub toolFavorites_DragEnter(ByVal sender As Object, ByVal e As DragEventArgs) _
Handles toolFavorites.DragEnter
If e.AllowedEffect = DragDropEffects.Copy AndAlso e.Data.GetDataPresent(GetType(ToolStripItem)) Then
e.Effect = DragDropEffects.Copy
End If
End Sub
Private Sub toolFavorites_DragDrop(ByVal sender As Object, ByVal e As DragEventArgs) _
Handles toolFavorites.DragDrop
Dim droppedItem As ToolStripItem
droppedItem = CType(e.Data.GetData(GetType(ToolStripItem)), ToolStripItem)
AddToFavorites(droppedItem)
End Sub
Private Sub AddToFavorites(ByVal item As ToolStripItem)
Dim newItem As ToolStripButton
newItem = New ToolStripButton(item.Text, item.Image)
newItem.Tag = item
AddHandler newItem.MouseDown, AddressOf FavoritesContext
AddHandler newItem.Click, AddressOf FavoritesClick
AddHandler item.EnabledChanged, AddressOf OnMenuEnabledChanged
toolFavorites.Items.Add(newItem)
End Sub
Then the code to route the click of the favorites to the real menu item
Private Sub FavoritesClick(ByVal s As Object, ByVal e As EventArgs)
Dim item As ToolStripItem
item = CType(CType(s, ToolStripItem).Tag, ToolStripMenuItem)
item.PerformClick()
End Sub
That was really it. Of course, then I had to persist the favorites in My.Settings and provide a way of removing the favorite menu item, resulting in the above-referenced AddHandler statement for FavoritesContext and a couple other methods for running through the menu items on the form load and close. Then we need to disable the favorite button when the linked menu item is disabled, leading to the AddHandler for OnMenuEnabledChanged. It just keeps growing.
I wanted to keep the post short, so if you’re interesting in these other pieces, I put a ZIP of the POC source at http://www.700cb.net/Downloads/DragMenus.zip. That something I usually don’t do. Interesting…
|
|
|
|
|
|
|
|
|
A recent event that made some blog headlines got me thinking about security and stuff. I’ve been thinking about it for a couple nights and every time I remember it, I am too tired to actually write anything. The event was some hacking and defamation of some Facebook sites, probably Christian users. I was online the night that the passwords got hacked and I remember making a post elsewhere warning people to change their passwords. Almost 6 months later, the attackers organized and used that information. From reading posts on other sites, there’s a few things wrong with the security model in place. The flaw used to be weak passwords (and still is, I’m sure). Another flaw was using the same password for multiple sites. The current flaw is the security question to reset a password, where the answer to that question can be found online, possibly posted by the victim. The attacker gains access to the victim’s email account, but can’t get to other accounts (Facebook, Paypal, banking, etc), so they answer the security questions to reset the password and have the new password sent to the email account they have access to. There’s two things I propose to squelch this. First, understand that hacking is best done in a hurry. The longer you mess around in someone’s account, the more likely it is you will get caught. So, delay the delivery of the new password for 24-48 hours. Ideally, this would be user-defined setting in a member profile. This is better understood when you know the other half of my security proposal. Sure, if someone forgets their password, they will have to wait 24-48 hours to get their new password. That is an inconvenience in the name of security, but resetting a password is a last-ditch effort. If someone logs in frequently, they know their password. The most common case would be someone who hasn’t logged in for many months. If they haven’t been active on the site for months, what’s an extra 24 hours? Ok, the second part of the improvement, probably the more important part: the password reset request is queued. You’d think that when a user hits “forgot password” and answers the security question, the password is reset and the email is generated. Well, now the email is delayed by a day or two. What happens is the user successfully logs in during that window of time? Yes, they do know their password; there is no need to reset it. The request can be cancelled and the user notified that a reset request was attempted. If the user thought their password was compromised, they would not use the Forgot Password/Reset Password function, they would change their password in the edit profile section after logging in. Think of it this way. You’re talking to a (soon to be untrustworthy) colleague. In the casual conversation, you mention what seems like innocent information about yourself, then you excuse yourself, saying that there’s a package at UPS you need to pick up. The colleague rushes to the UPS store and says “I’m John Smith from ABC Company. You have a package waiting for me?” If the clerk is bright, he’ll ask for ID. In lieu of ID, the imposter may or may not successfully convince the clerk he is you using the information gleaned from a casual conversation (or maybe from online stalking). What I am suggesting is that because positive proof couldn’t be made with an ID (your password), the clerk says “We’ll have your package ready at the close of day. Stop back then to pick it up.” Now if you show up half an hour later to pick up the package and show your ID (your password), the interception attempt was foiled and maybe you can catch the imposter when he comes back in later. This is why I suggest having the reset delay be user-configurable. It’s a balance of convenience vs. security (not unlike “remember me on this computer”). If a person knows they log in to a site at least once a day, 12 hours might be sufficient. If you only use the site once a month, maybe a 3 day delay with notifications emailed at random times telling you how much time is left. A hacker would have to monitor your email constantly and random times to intercept all the notifications. This idea came about from an old article I read about how you could get away with less restrictive password policies if you implemented an expiring lockout after X failed login attempts. The theory was, if an attacker is using a script or other random generating tool to brute force a login, stalling the login attempts for 15 minutes after every 5 failed logins makes the attack unrealistic because the Change Password policy would require the user to change the password before the brute force attack could finish all the possibilities. Then they’d have to start over. What a waste of time. These are just ideas, but as people start keeping and sharing more personal information online, security based on that personal information is less secure, duh. So by making the bad guys wait, the good guys have a chance at taking action. That’s probably why bank robbers run out the door instead of scanning the marketing brochures.
|
|
|
|
|
|
|
|
|
The other day I was writing some code and I got an error message. It happened to be one of those cases where it should have worked. You know when good code it written it just happens to work like you expect it to. The general idea was: Private Sub FillCombos()
Dim dict As New Dictionary(Of String, String)
With dict
.Add("Steve", "Steve Harris")
.Add("Bill", "Bill Murray")
.Add("Dave", "Dave's not here")
.Add("Jack", " (Captain) Jack Sparrow")
End With
ComboBox1.DataSource = dict.Keys
ComboBox2.DataSource = dict.Values
End Sub
Seems pretty logical. The Keys property of a dictionary is a collection and so is the Values. A collection is just a list of things. They should be bindable, right? Well, no. The error you get is: “Complex DataBinding accepts as a data source either an IList or an IListSource.” And after poking around, it’s right. The collections do not implement those interfaces.
So that was going to be a blog topic about how and why those collections couldn’t be databound, but when I tried it on my other computer, I noticed that I had extra methods on those collections, ToArray being one of them. so I used ToArray and the databinding worked. As it turns out, the ToArray was added from System.Linq.Enumerable, which comes along with .NET 3.0. I switched the project to .NET 2.0 and the ToArray function disappeared.
So now, I’m blogging more about the blunt realization that I’m officially missing out on additional functionality that would have simplified my code. Well, as complicated as this solution makes it:
ComboBox1.DataSource = New Generic.List(Of String)(dict.Keys)
ComboBox2.DataSource = New Generic.List(Of String)(dict.Values)
So, with the release of Windows 7, I do plan on installing VS 2008 at work and driving ahead with 64-bit development under .NET 3.5. Well, eventually. There’s still some Win2k workstations out there…
|
|
|
|
|
|
|
|
|
No, I certainly can’t help you get dates or have successful dates. But I can offer a couple of functions that might help you work with dates in your code. I suck at dates (all types). Every time I want to calculate dates, I need to print a calendar from Outlook and count the days. It’s pretty ironic that one of my previous projects was all about schedules. And one of my current projects deals with delivery schedules as well. So I can’t escape it. I always would get frustrated because every month was different. Every month started on a different day and had a different number of days. It felt impossible to get all the different combinations. But recently, I had a moment of clarity and realized some basic facts about a month. Things like: - No month has less than 28 days
- This guarantees every month will have 4 weeks
- This guarantees there are no less than 4 and no more than 5 of every weekday in a month
- The only weekdays that will have 5 occurrences will be the days in excess of 28. These days can be accounted for at the beginning or end of the month – it doesn’t matter.
- By extension, there are a minimum of 20 workdays in a month (Mon-Fri)
- And, any additional workdays will be those in excess of 28 that are between Monday and Friday
Earlier attempts to figure out the number of workdays in a month resulted in a brute force loop that would run through every day from 1 to 31 and if the DayOfWeek was Mon-Fri, increment a counter. Now, with these new guidelines, I can start at 20 and only deal with 0-3 excess days. Like with this function: Shared Function WorkdaysInMonth(ByVal d As Date) As Integer
Dim daysInMonth As Integer
Dim extraWeekDays As Integer
daysInMonth = New Date(d.Year, d.Month, 1).AddMonths(1).AddDays(-1).Day
For i As Integer = 1 To daysInMonth - 28
Select Case New Date(d.Year, d.Month, i).DayOfWeek
Case DayOfWeek.Monday, DayOfWeek.Tuesday, _
DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday
extraWeekDays += 1
End Select
Next
Return 20 + extraWeekDays
End Function
Using the other rules for weekdays, we know we only need to deal with the exceptions, to find out whether there are 5 weekdays in a month:
Private Shared Function NumberOfWeekdaysInMonth(ByVal weekday As DayOfWeek, ByVal referenceDate As Date) As Integer
Dim firstDay As DayOfWeek
Dim lastDay As DayOfWeek
firstDay = New Date(referenceDate.Year, referenceDate.Month, 1).DayOfWeek
lastDay = New Date(referenceDate.Year, referenceDate.Month, 1).AddMonths(1).AddDays(-1).DayOfWeek
If New Date(referenceDate.Year, referenceDate.Month, 1).AddMonths(1).AddDays(-1).Day = 28 Then
Return 4
ElseIf lastDay >= firstDay AndAlso weekday >= firstDay AndAlso weekday <= lastDay Then
Return 5
ElseIf lastDay < firstDay AndAlso weekday >= firstDay - 7 AndAlso weekday <= lastDay Then
Return 5
ElseIf lastDay < firstDay AndAlso weekday >= firstDay AndAlso weekday <= lastDay + 7 Then
Return 5
Else
Return 4
End If
End Function
That one got a bit hairy because is the extra days started at the end of the week with a high DayOfWeek value, and ended early in the week with a low DayOfWeek value, we had to compensate at each end. That’s the reasons for all the different IF conditions. There’s also a specific condition for February’s 28 days.
Finally, a function to determine the first, second, third, fourth, or fifth weekday in a month. Useful when calculating holidays like Labor Day and Thanksgiving.
Private Shared Function NthDayOfMonth(ByVal index As Integer, ByVal weekDay As DayOfWeek, _
ByVal referenceDate As Date) As Date
Dim firstDay As DayOfWeek
Dim dayOfMonth As Integer
firstDay = New Date(referenceDate.Year, referenceDate.Month, 1).DayOfWeek
dayOfMonth = (7 * (index - 1)) + _
CInt(IIf(firstDay > weekDay, 7 + weekDay - firstDay, weekDay - firstDay))
Return New Date(referenceDate.Year, referenceDate.Month, dayOfMonth + 1)
End Function
|
|
|
|
|
|
|
|
|
So many great adventures start with this line. I am just wrapping up my own. The task was simple: Upgrade the hard drive in my laptop. I was going from 80GB to 320GB. What’s so hard about that? You use Ghost, you have it resize the partition to the max and go. The First Attempt (5 hours, no success) I borrowed a SATA “toaster” – a USB device that has a SATA dock in it and hooked it up to the laptop. I booted a CD to Ghost. The software recognized the internal drive and the external USB drive. I took all the defaults. When it started, my hopes sank as I saw the time remaining climb and climb. Ok, so maybe copying 80GB over USB isn’t that great of an idea – I’ll accept blame for that. That would’ve been tolerable except that the copy failed with a sector write error with 15 minutes to go. Ok, back to work and we’ll pick this up later. Attempts 2-5 (or more.) This time I was a little smarter. I pulled the internal drive from the laptop and used the SATA connectors in my desktop to copy between the two drives. This completed the copy in only 40 minutes. However, after installing the new drive in the laptop, the copy would never boot. I tried each of the different options in Ghost, eventually giving up that I’d be able to extend the partition. None of them worked. The probable cause? It’s an HP laptop and the drive has a stupid recovery partition. Not only does it have the 5GB recovery partition, but also a tiny 2MB unknown partition that is ID 0, to start first. When doing a partition copy, Ghost never copied it. When doing a full sector copy, which should include it, the transition from the custom boot sector to the main partition never happened. Stepping Back and Research At this point, I’ve powered my desktop on and off more times that I have in over a year. I’ve been swapping drives back and forth so I can use my desktop for research and then again use the CD for booting Ghost. Somewhere along the way, I decided to buy another SATA cable ($20 from Best Buy and I still feel violated by it). I downloaded True Image and Disk Director and decided they wouldn’t do the job before even installing them. I read up on the latest Ghost and it seems to be just some backup software now. Then I hit upon Disk Copy from EaseUS. It’s free and seems to be self-contained in a bootable CD. A couple more reboots and downloads, including the very impressive ISO Recorder (also freeware) and I was ready to go. The Final Snail (an inside joke) Booting up to Disk Copy, I selected the sector copy option and let it run. It was a little slower than Ghost – instead of 2.2GB/minute, it was 1.7GB/min. That added an extra 10 minutes of time. But it paid off, because the new drive booted! I’m pleased enough now that I’m not concerned about resizing the original partition. I’m just going to create a new partition to hold my 60GB databases and other large files. So, in summary, the HP recovery partition makes cloning drives impossible using Ghost (version copyright 2003), but using Disk Copy from EaseUS was successful. EaseUS also has a freeware partition manager, but I didn’t feel like trying it. And the funny part about all this is that the laptop is going to be slicked as soon as Windows 7 is available on MSDN.
|
|
|
|
|
|
|
|
|
I had something that I thought was a simple task and got the solution for it, but when I researched it, I didn’t see a whole lot of other people talking about it. Actually, in my brief searches, I didn’t see anyone talking about it. So here goes. What do you do if you want to add a time to a date? Most people would say, “Duh. A date is a time. That’s why they call it DateTime.” But say for the sake of argument, you have two variables, one holds a date and one holds a time. The time portion is irrelevant in your date variable because you always use .ToShortDateString and likewise with the time variable always using .ToShortTimeString. Now you want them together. Take the following block of code: Dim baseDate As DateTime
Dim newDate As DateTime
baseDate = DateTime.Now
newDate = New Date(2012, 12, 20)
' Insert here
MsgBox(baseDate.ToString & " = " & newDate.ToString)
Which statement placed in “Insert here” will be the most efficient, judging by IL code generated?
' Version 1
newDate = newDate.AddHours(baseDate.Hour) _
.AddMinutes(baseDate.Minute) _
.AddSeconds(baseDate.Second) _
.AddMilliseconds(baseDate.Millisecond)
' Version 2
newDate = Date.Parse(newDate.ToShortDateString _
& " " & baseDate.ToLongTimeString)
' Version 3
newDate = newDate.Date.Add(baseDate.Subtract(baseDate.Date))
You might be surprised like I was, but #2 is the best, unless you need millisecond precision, then #2 is disqualified, making #3 the best. Probably no surprise, #1 is the worst because every .Add[interval] call creates a new temporary value in IL.
You might be looking at #1 thinking “Who would do something like that?” When I first had the need to add a date and a time, I considered that route for about a second and thought there has to be a better way. I settled on using #3, figuring if I added a time with zero days on it (by subtracting out the date portion) it would give me the TimeSpan structure I needed to add to the date portion of my original date.
TimeSpan is a really nice structure that probably doesn’t get as much exposure as some other .NET elements like StringBuilder.
|
|
|
|
|
|
|
|
|
You know, when you think of a popular website, run by a pretty big company, you would expect that they would have a pretty good software development team. If you think of the popularity of a website and how much impact poor code would have to their servers and how much impact a poor user experience would have to their customers, then it’s pretty clear, you need to have good code. I don’t usually like to crap all over someone’s coding. I will criticize and I think I am pretty good about being flexible about differences in coding styles. But sometimes there is a case that is SO flagrantly bad, it has to be called out. And the subject in this case is The Weather Channel. I recently noticed that the interactive weather map page had changed. And in this change, it stopped working in my browser of choice: Opera. The specific problems that caused me to dive into the code (and subsequently begin projectile vomiting) were that it would freeze partway though the page load (hitting stop would display the remainder of the page) and that the map animation would only load the first two images and then freeze. I never got to the point of troubleshooting them. Let’s start with the stats that will knock you on your ass. This page alone, just the HTML code, not the linked scripts, not the images, is 223k. It is 5500 lines of code. Can you fathom that? An immensely popular web page being an incredible 223 kb in size. Now can you imagine being the developer creating and maintaining a 5500-line web page? So, I saved off the HTML code and loaded it in VS. There was a slight problem with the Format Document function because of a misformatted tag (go figure). VS was nice enough to tell me which line the issue was on, so I could fix it. Awesome. I spent about 30 minutes reading through the code up until about line 1500. Then I thought I’d get some more basic stats. The inline JavaScript consumes over 3600 lines of the source. There were cases where the inline scripts were 520 lines, 423 lines, 400 lines, 300 lines and so on down. Do you feel ill yet, I sure do. The inline style sheets took up another 260 lines. Next, the comments left in the code indicate there’s something not so top-notch going on. A couple noteworthy samples: “Daniel Test for in the morning”, “Debugging Comments” “Combined html for optimization.” (oh, the irony) and the best one, “ADDED THIS CLOSING DIV BECAUSE IT WAS IN ORIGINAL CODE, CAN'T FIND MATCHING OPENING DIV” I can see Daniel has his work cut out for him. The part that makes me see red is that when you look at the 3600 lines of JavaScript, you learn that essentially you have been served “everything”. The scripts look at the URL you are on and determine what to display and how to display it. They look at the cookies and determine what ads to serve up (and damn is there a lot of ad code). They contain huge blocks of HTML code depending on what section you are in. For example, apparently, there is something called a “dashboard”, which is customized to whatever section you’re in. There’s Business & Travel, Boat & Beach, Golf, and Schoolday. Guess what? you got served the HTML for all four dashboards, and your browser will determine which gets displayed! Also, your browser figures out where you came from and displays special graphics depending on it, like if you came from Google or from Home Depot. and if you come from Home Depot and there’s a storm, you get different graphics. I don’t have a problem with most of that. It’s clever and creative to vary the look at feel according to your partners and tie it in to the current weather. But the base problem is: this can all be done on the server. Looking at the URL, looking at the cookies, showing and hiding elements of the page based on those values. It’s like code from 1990 – it’s not even ASP-grade code. Even your typical ASP programmer would filter out the HTML for sections that aren’t relevant. I just don’t get it. It absolutely boggles the mind that a major web site could have a page that is so grossly inefficient as to possibly cripple the web servers that serve it. And can you imagine the bandwidth costs? The web page code specifically forces you to get all the linked scripts using what they term “cache-busting”. I’m starting to feel ill again, so I’m going to stop now.
|
|
|
|
|
|
|
|
|
I suppose, like many companies, we’re trying to stay under the radar from the roving hammers of economic suckdom. I also think we’re doing pretty well at it. Possibly like other companies, we are taking the opportunity to completely rethink a core business need: communication. I am confident that anyone who spends a decent time with an employer eventually will say “communication in our company isn’t that great.” It’s probably inevitable that as a company grows, the individual teams focus harder on what they are good at, maybe because of time constraints, maybe corporate culture turning introverted, maybe something else. Anyway, it happens to the best. It’s also something I have a decent interest in. I enjoy sharing knowledge and information and sometimes have felt frustrated that there wasn’t a decent outlet for me to do so. Well, like they say, be careful what you wish for. Seemingly overnight our company has embraced three new initiatives that will hopefully foster communication. The first one is an installation of Microsoft Office Communication Server. That’s a big name for a little service: IM. Well, MOCS (ahhh… acronyms) does a lot more than just Instant Messaging, we’re using video conferencing and other Live Meeting functions too. But the one that I cared about was IM. Email is just so heavy to ask a simple question or send a code sample. MOCS was decided upon after trying out three different video conferencing systems. This was decided within about a week. So within two weeks, we had a new communication and conferencing solution. Back to the original point, the next thing we adopted was a centralized bug tracking system, BugTracker.NET. This was a surprise to me, because I downloaded it as a simple replacement to my spiral-bound notebook. I’ve been writing things down for years now and have recently learned that it doesn’t help me at all once I turn the page. Going back a year in my notes, I logged 60 open issues into BugTracker. I explained to my boss what I was doing, he spent about 30 minutes with it and said that this was the new standard for the department. So, within a day, we had a new issue tracking system for hardware and software issues. Thirdly, during a brainstorm session on how our department can be more friendly to our customers, the end users, it was suggested we maintain blogs. This was pretty exciting to me because it opened the door to things I really wanted, like support forums and wiki knowledge bases. But blogging is a start. I was given the lead for it and selected Community Server since I had experience in maintaining one and using one (here!). So within a day, we had set up an online community for our department. Only our department will be blogging initially, but we were challenged to set the bar high for the rest of the company. I expect other departments will quickly embrace the blog concept. So let’s recap. We’re a five-person IT staff supporting over 300 users at 45 locations. On the request of our new leadership, we evaluated, selected, installed a corporate collaboration system; implemented what can easily become our corporate Intranet; and started tracking issues department-wide, answering the question, “where do we stand”. All this in the span of a few weeks, while still supporting the users. We’ve had a little time to absorb these changes, but looking back, this is a major shift for a company. These kinds of changes usually happen over months. Next week will be presentations on what we’ve done. I’m not sure how many heads will explode.
|
|
|
|
|
|
|
|
|
I recently had a little piece of SQL code that I thought I would post and it reminded me of something I did a long time ago (5 yrs) and thought I'd write something on it. Anyway, first here's the little piece without much celebration. This SQL code answers the question "What date is the third Wednesday in November 2008?" This code could be wrapped in a function pretty easily. declare @month int,@year int,@weekday int,@weekOffset int
set @month=11 -- (Month of date to check)
set @year=2008 -- (Year of date to check)
set @weekday=5 -- 1-7 (Sun-Sat)
set @weekOffset=1 -- 1-5 (Week of month)
declare @currentMonth datetime
declare @firstDay int
declare @newDate datetime
-- get the first day of the month
set @currentMonth=cast(cast(@month as char)+'/1/'+cast(@year as char) as datetime)
set @firstDay=datepart(dw,@currentmonth)
-- Add the number of days from the first day
set @newDate=dateadd(dd,7*(@weekOffset-1)+
case
when @weekday>=@firstDay then @weekday-@firstDay
when @weekday<@firstDay then 7+@weekday-@firstDay
end, @currentmonth)
select @newdate
The bigger idea I had was for recurring events in SQL, like for a calendar. Assume the following structure of an event table
CREATE TABLE dbo.Events(
ID int IDENTITY(1,1) NOT NULL ,
StartDate datetime,
EndDate datetime,
Title varchar(255),
Description varchar(2000),
RepeatValue char(1),
RepeatInterval int
)
So you have a bunch of events defined that start and end on a range of dates. The RepeatValue would be: D,W,M,Y for Day, Week, Month, Year. The RepeatInterval would be the number of Days/Weeks/Months between repeats. Add in some sample data
insert events select '1/1/08','12/31/08','Each Month All Year','','M',1
insert events select '1/1/08','6/30/08','Bi-weekly Half Year','','W',2
insert events select '7/1/08','12/31/08','Weekly Half Year','','W',1
insert events select '2/1/08','3/1/08','Every other Day in Feb','','D',2
insert events select '7/4/08','12/31/2999','July 4th','','Y',1
This is the stored proc I came up with to return the events for a given month:
CREATE Procedure [dbo].[sp_ListRecurringCalendarEvents]
@viewdate datetime
as
set nocount on
declare @checkdate datetime
create table #tempCalendar(
id int,
startdate datetime,
enddate datetime,
title varchar(255),
recurrance bit default 0
)
-- Insert Standard events
insert #tempCalendar
select e.id,e.startdate,e.enddate,e.title,0
from events e
where datepart(mm,e.startdate)=datepart(mm,@viewdate)
and datepart(yy,e.startdate)=datepart(yy,@viewdate)
and repeatinterval=0
-- Insert all recurrances
set @checkdate=cast(cast(datepart(mm,@viewdate) as char)
+ '/1/'
+ cast(datepart(yy,@viewdate) as char) as datetime)
-- Loop through each day in month and see if it is a recurrance
while datepart(mm,@viewdate)=datepart(mm,@checkdate)
begin
insert #tempCalendar
select e.id,
case when RepeatValue='D' then dateadd(dd,datediff(dd,startdate,@checkdate),startdate)
when RepeatValue='W' then dateadd(ww,datediff(ww,startdate,@checkdate),startdate)
when RepeatValue='M' then dateadd(mm,datediff(mm,startdate,@checkdate),startdate)
when RepeatValue='Y' then dateadd(yy,datediff(yy,startdate,@checkdate),startdate)
end,
e.enddate,e.title,
case when @checkdate=e.startdate then 0 else 1 end
from events e
where e.RepeatInterval<>0
and
-- Daily (Check to see if the number of day interval matches
case when RepeatValue='D' then (cast(datediff(dd,startdate,@checkdate) as decimal)
/cast(RepeatInterval as decimal)) else 1 end =
case when RepeatValue='D' then (datediff(dd,startdate,@checkdate)/RepeatInterval) else 1 end
and
-- Weekly (check to see if the week interval matches and the weekday is the same)
case when RepeatValue='W' then (cast(datediff(ww,startdate,@checkdate) as decimal)
/cast(RepeatInterval as decimal)) else 1 end =
case when RepeatValue='W' then (datediff(ww,startdate,@checkdate)/RepeatInterval) else 1 end
and case when RepeatValue='W' then datepart(dw,startdate) else 1 end =
case when RepeatValue='W' then datepart(dw,@checkdate) else 1 end
and
-- Monthly (check to see that the month interval is the same and the day of the month is the same)
case when RepeatValue='M' then (cast(datediff(mm,startdate,@checkdate) as decimal)
/cast(RepeatInterval as decimal)) else 1 end =
case when RepeatValue='M' then (datediff(mm,startdate,@checkdate)/RepeatInterval) else 1 end
and case when RepeatValue='M' then datepart(dd,startdate) else 1 end =
case when RepeatValue='M' then datepart(dd,@checkdate) else 1 end
and
-- Yearly (Check to see if the day of the year is the same)
case when RepeatValue='Y' then datepart(dy,startdate) else 1 end =
case when RepeatValue='Y' then datepart(dy,@checkdate) else 1 end
and
e.startdate <= case when RepeatValue='D' then dateadd(dd,datediff(dd,startdate,@checkdate),startdate)
when RepeatValue='W' then dateadd(ww,datediff(ww,startdate,@checkdate),startdate)
when RepeatValue='M' then dateadd(mm,datediff(mm,startdate,@checkdate),startdate)
when RepeatValue='Y' then dateadd(yy,datediff(yy,startdate,@checkdate),startdate)
end
and
e.enddate >= case when RepeatValue='D' then dateadd(dd,datediff(dd,startdate,@checkdate),startdate)
when RepeatValue='W' then dateadd(ww,datediff(ww,startdate,@checkdate),startdate)
when RepeatValue='M' then dateadd(mm,datediff(mm,startdate,@checkdate),startdate)
when RepeatValue='Y' then dateadd(yy,datediff(yy,startdate,@checkdate),startdate)
end
-- Increment the date
set @checkdate=dateadd(dd,1,@checkdate)
end
-- return the results
select * from #tempCalendar
-- Clean up
drop table #tempCalendar
The proc is called passing a date within the month to be displayed. The results include the ID of the event (for linking), the start date of the event, the end date of the event (not so relevant), the event title, and whether this is a recurring event or not (in case you want to display it differently or disable editing of events that aren't the original).
So, many years later, I still find it intriguing that I was doing stuff like that.
|
|
|
|
|
|
|
|
|
It was asked of me once: Why isn't my "please wait" form doing anything? The form had an animation, but the animation never moved. The main form code would show the "please wait" form, run some code, then hide the "please wait" form. As it turned out, the "please wait" form was being blocked by a database call and some other stuff in the main form. There was never a chance for a DoEvents or redraw or anything. The quick answer is, you have to show the other form in another thread, so it can operate freely and redraw itself. The longer answer is, you have to deal with threading. Threading is very easy in VB.NET, but some people want answers, not lessons, so I ended up writing this quick class to show a form in another thread. It's pretty simple. You instantiate the class by passing in an instance of your "please wait" form , then call show(), do your work, then call hide(). Like so: Dim f as New BackgroundForm(new frmWait)
f.Show()
' Do some stuff
f.Hide()
Only one caveat: you can't have the variable for the BackgroundForm class at the class level. It must be declared, instantiated, and used in the same method. Otherwise, you will get Cross-Threading errors.
So, here's the class you can use for yourself, if you prefer answers to lessons. Public Class BackgroundForm
Dim displayForm As Form
Dim t As System.Threading.Thread
Dim isActive As Boolean
Shared lockObject as New Object
Public Sub New(ByVal f As Form)
displayForm = f
End Sub
Public Sub Show()
t = New System.Threading.Thread(AddressOf ShowForm)
t.IsBackground = True
t.Start()
isActive = True
End Sub
Public Sub Hide()
isActive = False
End Sub
Private Sub ShowForm()
displayForm.Show()
Do While isActive AndAlso displayForm.Visible
System.Threading.Thread.Sleep(100)
SyncLock lockObject
Application.DoEvents()
End SyncLock
Loop
End Sub
End Class
I made a slight change to the code to protect against thread stomping. If you have an animated GIF in your "please wait" form, and you have a more than one "please wait" form active at once, there is the chance you will get a "object is in use elsewhere" error. This happens when the image for the current form is being changed - in the DoEvents - but another form is being closed/disposed, taking the same image with it.
By wrapping the DoEvents in a SyncLock that is shared among all instances, this is prevented. I couldn't get the error to happen unless I opened over 20 windows and had them closing at scheduled times, but your luck may be worse.
|
|
|
|
|
|
|
|
|
It's nice that .NET controls have an auto-size property so you don't have to worry about overflow and all. But what about cases where you have a fixed layout? Well, that's simple, you turn autosize off and fix the control to the size you need. That's half the story. What about the text that's inside it? Now you know I'm talking to marketing people when I say that there are times you want the text to be as big as possible within that control. But you can't just set the font to a huge size, because sometimes you'll have more text to display and the font size must sadly be reduced. To accommodate this, I made a quick method that brute-forces the correct font size in the control. basically, stepping down the size of the font until it fits. I know loops like this are cheap, poor programming, and I did give consideration to doing some hard math to calculate the proper font size based on the initial size, but sometimes not getting hung up on performance can be liberating. Private Sub ResizeText(ByVal c As Control)
Dim currentSize As Size
Dim currentFont As Font
currentFont = c.Font
Do
currentSize = TextRenderer.MeasureText(c.Text, currentFont, _
c.Size, TextFormatFlags.WordBreak)
If currentSize.Width > (c.Width - c.Margin.Horizontal) _
OrElse currentSize.Height > (c.Height - c.Margin.Vertical) Then
currentFont = New Font(currentFont.FontFamily, _
CSng(currentFont.Size - 0.5), currentFont.Style, currentFont.Unit)
Else
Exit Do
End If
Loop While currentFont.Size >= 1
c.Font = currentFont
End Sub
|
|
|
|
|
|
|
|
|
When binding business objects to a datagrid, often you have a need to display some information that is not directly exposed by the object itself. Maybe it's a calculated value, maybe it's something nested deeper in the object. When faced with this issue, there are a few different action paths you can take. You can add extra read-only properties to your business object to support the extra view information. You can create a new class that inherits from the class you are displaying and put the extra properties in there. Or you can handle the CellFormatting event in the datagrid and change the displayed values manually. One of the downsides of using a new derived class with extra properties is that you can't cast a base class to it. You could cast down to the base class, but no casting up. Here is a technique that is closest to the second option listed above and side-steps the upcasting problem. I dislike the first option because it clutters the business object with UI-specific code. Going with option 2 is only slightly better, while you can populate the correct display-specific object and return it from your business logic layer, either you have to have a method that return the derived type, or you will have to cast it to its correct type in the UI. Even then, your business layer still contains UI logic. So, keeping things separated, the derived display-specific class should be defined in the UI layer. This means it will have extra read-only properties for use with databinding. The business layer will return the basic object(s), so it will be up to us to convert these to UI-friendly versions. There are two problems with converting the object: not all the object state may be exposed via public properties, and those properties may contain logic. It would be best to copy the object by its internal state - private variables. On first thought, working with the private variables means the code must be inside the source object and the destination object. This would be tedious to do, passing in the destination object, then sending the source object's private variables to the destination so the destination object can manipulate its own private variables. Yuck. However, using Reflection, the job gets a whole lot easier. Here's a small class with a method to convert one class to another by copying its private and public fields. The properties are intentionally excluded since they may contain logic that modifies the internal state. You should use this technique with care and know exactly what it does and does not do. Basically, it copies values from one instance of a class to another. This is fine for simple classes, but it's not going to resolve references for you. Consider ClassA with a private field of type ClassB. ClassB maintains a private variable with a reference to ClassA, so that it can manipulate all of its "parent's" state and logic. If you use this technique to cast ClassA to ClassAA, because you want an extra property to display some info from ClassB, you're in for some fun results if you change some data in ClassAA. This is because ClassB still has a reference to ClassA, not ClassAA. Public Class UpCaster
Shared Sub CastUp(ByVal sourceObj As Object, ByVal destinationObj As Object)
Dim values As New Dictionary(Of String, Object)
Dim props() As Reflection.FieldInfo
props = sourceObj.GetType.GetFields(Reflection.BindingFlags.NonPublic _
Or Reflection.BindingFlags.Static _
Or Reflection.BindingFlags.Instance _
Or Reflection.BindingFlags.Public)
For Each p As Reflection.FieldInfo In props
values.Add(p.Name, p.GetValue(sourceObj))
Next
props = destinationObj.GetType.GetFields(Reflection.BindingFlags.NonPublic _
Or Reflection.BindingFlags.Static _
Or Reflection.BindingFlags.Instance _
Or Reflection.BindingFlags.Public)
For Each p As Reflection.FieldInfo In props
If values.ContainsKey(p.Name) Then p.SetValue(destinationObj, values(p.Name))
Next
End Sub
End Class
|
|
|
|
|
|
|
More Posts Next page »
|
|