Tuesday, August 29, 2006
Appointment ListChanged Event
Has anyone been able to receive an event when you've registered to receive events from ListChanged?
Here is the code I'm using (I'm using VS 2005 RC1 with CF 1.0):
OutlookSession outlook = new OutlookSession();
outlook.Appointments.Items.ListChanged += new
ListChangedEventHandler(Items_ListChanged);
This code runs just fine, but changing the calendar doesn't fire this event in my application. I've searched the net, but haven't found anyone who appears to have used it.
Peter Foot, from InTheHand fame, then responded:
I haven't checked, but it's possible that the events in Microsoft.WindowsMobile.PocketOutlook only fire when the changes are made through these APIs not when changed from another application...
The original poster, however, corrected Peter with information from the MSDN docs:
Here's what the docs say:
"Occurs when either the collection changes, or when an individual PIM item in the collection changes."
Additionally, the MSDN article "What's New for Developers in Windows Mobile 5.0" says that you can track changes made to Pocket Outlook folders using managed code and gives an example, so it sounds like it should show changes made by other applications. As far as I can tell, that's the whole idea, in fact.
But I can't get it to trigger any events.
SystemState CalendarAppointment Events Seem Very Broken
For the most part, I've had good experiences with the new Microsoft.WindowsMobile.Status.SystemState infrastructure in .NET CF 2.0. However, recently I've run up against numerous issues with the CalendarAppointment* and CalendarNextAppointment* events. Incidentally, I've tested this on the I-Mate KJAM Pocket PC Phone as well as the Cingular 2125, T-Mobile SDA, and the I-Mate SP5m SmartPhones. It shouldn't be too hard to repo what I'm describing on your own device.
I will describe the problems in descending importance:
1. The SystemState calendar related events do not properly notify subscribers when the calendar appointment state changes. My impression of these system states is that an event will be fired throughout the day as a user's calendar appointments occur. I would say that in more than 90% of the cases, no event is triggered. I haven't been able to diagnose exactly why this is. The problem I'm describing here seems relevant for all calendar related events. For example,
private SystemState _stateCalendarAppointmentBusyStatus;
private void Init()
{
_stateCalendarAppointmentBusyStatus = new
SystemState(SystemProperty.CalendarAppointmentBusyStatus, true);
_stateCalendarAppointmentBusyStatus.Changed += new
ChangeEventHandler(OnStateCalendarAppointmentBusyStatusChanged);
ChangeEventArgs args)
{
AppendText(SystemProperty.CalendarAppointmentBusyStatus,
(BusyStatus)args.NewValue);
{
_textBox.Text += systemProperty + ": " + newValue + "\r\n";
2. At this point, I thought perhaps I misunderstood what the current calendar appointment events did particularly because I seemed to get events when I would go into the calendar and modify the current appointment. However, this isn't the issue. I setup a polling sensor that would, instead of relying on the events, constantly poll the calendar related system states. You can do this because calling SystemState.CalendarAppointment, for example, returns the current value of that system state. However, what I found through polling was that this system state was not actually changing. To test this out, setup a "refresh" button in your GUI or run a windows Timer object to spit out the current state of the CalendarAppointment* system states. Then, add 5 or 6 test appointments to your calendar that will occur over the course of the next 10 minutes. You'll note that the states don't actually change with the calendar appointments. I've found that the CalendarAppointment* and CalendarNextAppointment* seem to be particularly troublesome while CalendarHomeScreenAppointment* stuff works a little more frequently (though these events are much less useful to us).
3. Going back to using the system state events. Interestingly, the following code will sometimes throw a NullReferenceException because the args object passed in is null. Perhaps this makes sense--if the current appointment is deleted the BusyStatus switches from a known state to a null state--however, this is underdocumented. An unsavvy programmer could easily fail to null check the ChangeEventArgs argument.
private void OnStateCalendarAppointmentBusyStatusChanged(object sender,
ChangeEventArgs args)
{
AppendText(SystemProperty.CalendarAppointmentBusyStatus,
(BusyStatus)args.NewValue);
UPDATE 09/11/2007: I received word from Microsoft today that this is indeed a bug in WM 5.0. Direct quote from the Microsoft employee, "I’ve confirmed the bug on WM 5.0, but it seems to be fixed in WM 6.0. That is, that the Notification Broker (and associated registry settings) don’t update properly when the calendar changes."
Friday, August 25, 2006
Issues with SystemState.Date
The first relates to SystemProperty.Date. Subscribing to the SystemProperty.Date results in a ChangedEventArgs object with the wrong value type, or, at least a value type that is inconsistent with documentation. Even the SystemState.Date type (which is of type System.DateTime) would leave you to believe that the ChangedEventArgs.NewValue object would be of type DateTime. In this case, however, it's actually a byte array of length 8. We believe this represents milliseconds in UTC time (8 bytes in C# is a long type). See this Date and Time article at MSDN for more on working with date and time. This typing issue results in a InvalidCastException in the following code.
private SystemState _stateDate;
cstr(){
_stateDate = new SystemState(SystemProperty.Date);
stateDate.Changed += new ChangeEventHandler(OnStateDateChanged);
}
private void OnStateDateChanged(object sender, ChangeEventArgs args){
//The code below will throw an exception as the args.NewValue type is actually
//a byte array of size 8! This is undocumented and unexplained.
DateTime date = ((DateTime)args.NewValue);
}
I have not comprehensively explored this issue. Other than the fact that the arg.NewValue is a byte array of length 8, I cannot elaborate. It is only conjecture that args.NewValue here is a timestamp in milliseconds at UTC. Regardless, it seems to contradict documentation and go against the paradigm established by other SystemState functions.
One final note about this state. The SystemState.Date docs say:
Gets the current date. This Date/Time value changes once per day. Monitor this property to perform actions on a daily basis. Do not use Date to get an accurate clock time; instead, use Time.
The documentation does not explicitly point out the strategy or time of day that SystemState.Date changes, only that it changes daily. I had hoped that it would change at midnight each and every day; however, what we found was that SystemState.Date appeared to change at midnight GMT (or 5PM PST). So, even if you were to schedule a daily routine with this property, it wouldn't necessarily be at the beginning of the day. Given this limitation, we decided to roll our own SystemState.Date event class that fires at midnight each day.
If I get a chance this weekend, I'd like to follow up this post with the problems we've experienced at my lab with the SystemState.CalendarAppointment* properties. They don't appear to change at all no matter what the calendar/appointment structure is like on the phone.
Update February 9th, 3:15AM: My suspicions about the 8-byte array were somewhat correct. It is a representation of DateTime. This is how you properly convert the byte array to a DateTime object:
byte[] rawValue = SystemState.GetValue (SystemProperty.Time) as byte[];
long value = BitConverter.ToInt64 (rawValue, 0);
DateTime time = DateTime.FromFileTime (value);
Tuesday, August 22, 2006
Size Limit of SQL Server 2005 Mobile Database
Size limit of SQL Server Everywhere Edition database: 4 Gigabytes.
(link)
Monday, August 21, 2006
SqlCeParameter Bug?
The following SqlCeCommand throws an exception. Is this a bug or am I doing something wrong?
SqlCeCommand sqlCmd = sqlConn.CreateCommand();
sqlCmd.CommandText = String.Format("INSERT INTO {0} (UserId, VersionId, {1}, PropertyName, PropertyValue, PropertyValueType) VALUES (@userid, @versionid, @headercolumnval, @propertyname, @propertyvalue, @propertyvaluetype)", strTable, headerName);
String strValue = p.Value.ToString();
sqlCmd.Parameters.Add("@userid", SqlDbType.NVarChar, 255, "UserId").Value = Globals.UserId;
sqlCmd.Parameters.Add("@versionid", SqlDbType.BigInt, 8, "VersionId").Value = Globals.VersionId;
sqlCmd.Parameters.Add("@headercolumnval", SqlDbType.NVarChar, 255, headerName).Value = headerColumnValue;
sqlCmd.Parameters.Add("@propertyname", SqlDbType.NVarChar, 255, "PropertyName").Value = p.Key;
sqlCmd.Parameters.Add("@propertyvalue", SqlDbType.NVarChar, 4000, "PropertyValue").Value = strValue;
sqlCmd.Parameters.Add("@propertyvaluetype", SqlDbType.NVarChar, 255, "PropertyValueType").Value = p.Type.FullName;
sqlCmd.Transaction = sqlTransact;
sqlCmd.Prepare();
sqlCmd.ExecuteNonQuery();
sqlCmd.Dispose();
Throws the following exception at the sqlCmd.ExecuteNonQuery() line:
System.InvalidOperationException was unhandled
Message="@propertyvalue : String truncation: max=255, len=463, value="(removed for posting clarity)"."
StackTrace:
at System.Data.SqlServerCe.SqlCeCommand.FillParameterDataBindings()
at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommandText()
at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand()
at System.Data.SqlServerCe.SqlCeCommand.ExecuteNonQuery()
at MyExperience.IO.DatabasePopulator.InsertProperty()
at MyExperience.IO.DatabasePopulator.ParseTriggers()
at MyExperience.IO.DatabasePopulator.Populate()
at MyExperience.MyExperienceFramework.Initialize()
at MyExperience.Application.MainForm.OnLoad()
at System.Windows.Forms.Form._SetVisibleNotify()
at System.Windows.Forms.Control.set_Visible()
at OpenNETCF.Windows.Forms.Application2.RunMessageLoop()
at OpenNETCF.Windows.Forms.Application2.Run()
at MyExperience.Application.Program.Main()
However, if I change the line:
sqlCmd.Parameters.Add("@propertyvalue", SqlDbType.NVarChar, 4000, "PropertyValue").Value = strValue;
to
sqlCmd.Parameters.Add("@propertyvalue", SqlDbType.NVarChar).Value = strValue;
no exception is thrown and, better yet, the full amount of data actually makes it into the database. That is, the value is not truncated to the 255 length that Sql somehow thinks exists as a constraint.
Note that my schema for this table looks like:
TableName:
TriggerProperties
Columns:
UserId (PK, nvarchar(255), not null)
VersionId (PK, nvarchar(255), not null)
TriggerName (PK, nvarchar(255), not null)
PropertyName (PK, nvarchar(255), not null)
PropertyValue (nvarchar(4000), not null)
PropertyValueType (nvarchar(255), not null)
I followed this post up with this one on August 18th:
Ah, it looks like SqlCeParameter forces the Size property to 255. That's strange given the fact that the docs say, as previously mentioned, that the nvarchar data type can range from 1 to 4000 characters.
However, bear in mind, that by simply avoiding setting the Size property (either via the constructor or the property itself) allows one to INSERT nvarchar data much larger than 255. In other words, it looks like this "bug" is at the parameter level and not database level.
This is the test code:
for (int i = 0; i <= 500; i+=100){ SqlCeParameter param = new SqlCeParameter("@Test", SqlDbType.NVarChar, i); Debug.WriteLine(i + ": " + param.Size);
//Just in case the property acts differently, set it and print again
param.Size = i;
Debug.WriteLine(i + ": " + param.Size);
}
0: 0
0: 0
100: 100
100: 100
200: 200
200: 200
300: 255
300: 255
400: 255
400: 255
500: 255
500: 255
Any input from MS (or MVPs) on this? I'm far from being a seasoned C# SQL developer so maybe I'm doing something wrong.
Unfortunately I haven't heard any substantive comments back on usenet from MS or otherwise. Anyone in the blogosphere have an idea?
Update 8/25/2006 @ 11:08PM: On August 21st, I e-mailed the SQL Server Everywhere team via their contact page about this issue. Here's what I said:
Not sure how appropriate it is to contact you about problems I'm experiencing with Sql Mobile 2005, but I thought I'd give this a shot. If this is the wrong venue of communication, just ignore this plead for help.I'm having issues with the SqlCeParameter class, particularly with regards to how it handles setting the Size property. I can't seem to set the size to anything larger than 255 for the nvarchar datatype.
Please see my usenet post for more details (unfortunately no one with any expertise in this area has responded yet): direct link here
I received a response on August 24th:
Yes Jon you are right that there seems to be some problem with larger than 255 chars. I just tried and could repro it.
I shall come back to you on this after some more digging around to see if there is any work around.
To which I responded:
Thank you so much for the response. I was really hoping I'd hear back from someone at Microsoft to help me with this.
The work around that I've come up with is to simply leave out specifying the length parameter in the SqlCeParameter object. So, for my SQL data columns that are of type nvarchar and length > 255 I do this:
SqlCeParameter param = new SqlCeParameter("@custid", SqlDbType.NVarChar);
Rather than explicitly including a length and column:
SqlCeParameter param = new SqlCeParameter("@custid", SqlDbType.NVarChar, 2000, “CustomerID”);
You’ll note that in the former case your data can be of length > 255 and, critically, it will still make it into the database. In the latter case, however, you incur an exception.
Perhaps my workaround will shed some light on the underlying problem.
On August 25th I received another response:
I found the work around you used works perfectly and some of the internal apps also use it the same way as you mentioned. However, while digging I found that you can not use parameterized query when you have image/ntext columns (Large Object Datatype). Will update you more once I get some clarity.
To which I responded:
Hmm, I also use image data types in my SQL Mobile 2005 database and it appears to be working. In fact, a parameterized query, I believe, is the only way to get binary data into the database. What was the issue you ran into here?
Saturday, August 19, 2006
Nullable Types
More on the C# 2.0 Nullable Types
Jon Skeet's Blog : Elegant comparisons with the null coalescing operator
Jon Skeet's Blog : Nullable types and the null coalescing operator
MSDN Reference : ?? Operator (C# Reference)
MSDN Reference : Nullable Types (C# Programming Guide)
Friday, August 18, 2006
Recently Published Mobile Device Downloads
Hard Reset T-Mobile SDA
Update 08/26/2007: I couldn't get this to work on my Cingular 2125 device. I finally figured out what I was doing wrong; I held down the power button too long. You do not need to hold the power button down until you see the boot screen... simply press it for 0.5-1 seconds. Also, Mike Poulson points out there are two ways to reset the HTC Torando/Faraday-based SmartPhone.
The first way requires you to be able to boot the phone and get to the start menu. The second is if you cannot do this (i.e., forgot PIN or will not boot all the way).
First way:
Second way:
1. Remove Battery
3. Hold down right and left soft keys
4. Press Power button for ~1.45 seconds then release Power button only
5. Confirm prompt to reset
Monday, August 14, 2006
Usage of SqlCeCommand.Parameters
I'm curious about the Parameters property in SqlCeCommand. Is this the preferred/best usage of the SqlCeCommand object? I suppose one benefit of using Parameters is the ability to set binary/image data (as those are byte arrays) for insertions/updates. Are there other benefits? I tend to format all of my command statements like the following:
SqlCeCommand cmd = sqlConn.CreateCommand();
cmd.CommandText = String.Format("INSERT INTO {0} (ActionName,
ActionType) VALUES ('{1}', '{2}')", "Actions", actionName, actionType);
cmd.Transaction = sqlTransact;
cmd.ExecuteNonQuery();
instead of
SqlCeCommand cmd = sqlConn.CreateCommand();
cmd.CommandText = "INSERT INTO @tablename (ActionName, ActionType)
VALUES (@actionname, @actiontype)";
cmd.Parameters["@tablename"].Value = "Actions";
cmd.Parameters["@actionname"].Value = actionName;
cmd.Parameters["@actiontype"].Value = actionType;
cmd.Transaction = sqlTransact;
cmd.ExecuteNonQuery();
Ilya Tumanov, a member of the .NET Compact Framework Team, responded with:
Yes, parameterized query is a preferred way. Benefits are:
- No need to create bunch of string objects all the time (in your code).
- No need to convert parameters from binaries to strings (in your code).
- No need to parse strings to get binaries back (in SQL CE).
- No locale/format specific issues as binary is format less and locale independent.
- No need to redo each command from scratch, prepared execution plan can be used with just different parameters.
- Works for all data types including binary/image.
(link)
Update 08/16/2006 12:57AM: I followed up the above post with:
Thanks so much for this thorough response--it clarified much for me. One follow-up question, what do you mean by "No need to redo each command from scratch, prepared execution plan can be used with just different parameters?" This is most certainly my naivete here with respect to SQL; a short elaboration would probably suffice.
Illya once again responded:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/sqlc...
Preparing Commands
To be able to execute a query, the database engine must first parse, compile, and optimize the SQL statement. Often, this work can be done once if the command is to be executed multiple times, potentially saving time. If clients expect to run a query more than once, it is recommended that the command be prepared once, then call Execute multiple times. This should maximize performance by avoiding query recompilation. Commands can be prepared prior to execution by calling ICommandPrepare::Prepare. This is equivalent to compiling the command.
Friday, August 11, 2006
Reduce Size of .NET Executables
The technique we used for compressing .NET applications does not work with .NET Compact Framework (CF) [17]. The .NET CF does not implement the methods of the Assembly and AppDomain classes that make this technique possible. However, the devices that run .NET CF use compression by default for storing programs and data in the non-volatile device memory ([17] reports up to 2:1 compression ratio). This means that this technique may not be directly needed in .NET CF devices9.
[17] P. Yao and D. Durant. .NET Compact Framework Programming with C#. Addison Wesley, 2004.