« December 2006 | Main | February 2007 »

January 29, 2007

Adobe Lightroom 1.0 - $199 US till April 30, 2007

If you use Windows and wanted a version of Apple's Aperture for Windows ... well, you can't have that. Instead, you might be interested in Adobe's Lightroom product.

Adobe announced today a mid-February release of Adobe Photoshop Lightroom 1.0 (yes, they had to throw Photoshop in there!). Until April 30, 2007, there's a $100 discount. The odd wording though:

Special Introductory offer is available for the new product Adobe Photoshop Lightroom, which has not previously been sold. Special Introductory Offer valid in US, Canada and Mexico only. Offer valid until April 30, 2007, thereafter Adobe Store full retail price shall apply. Special Offer is $100 US off the expected Adobe Store full retail price of $299 US. Special introductory pricing excludes all taxes and shipping. Offer subject to change without notice. Void where prohibited.

Emphasis mine. Are they planning on changing the price still?

January 16, 2007

How to display a formatted number in WPF

I needed to format a double into a sane format for an application I'm working on.

The data is somewhat more precise than I need.

I wanted to format the exposure time more reasonably with only a few decimal points rather than the 4000 digit number I seemed to be getting ... . So, a handy value Converter was needed to fix the problem:

    [ValueConversion(typeof(object), typeof(string))]
    public class ToStringFormatConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string fmt = parameter as string;
            if (!string.IsNullOrEmpty(fmt))
            {
                return string.Format(culture, fmt, value);
            }
            else
            {
                return value.ToString();
            }
        }
 
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return null;
        }
        
    }

Then, using Binding and the Converter:

<TextBlock Text="{Binding Path=ExposureTime, Converter={StaticResource stringFormatConverter}, ConverterParameter={}{0:#0.0##}}" />

I needed to add to the resources section of my XAML file, an instance of this object:

<local:ToStringFormatConverter x:Key="stringFormatConverter"/>

The local namespace was added to the Window class (it could be added to any other root level WPF element like a page, etc):

xmlns:local="clr-namespace:MyApplicationNamespace"

To adjust the format, just change the ConverterParameter format shown above in red ({0:#0.0##}). This is just the normal ToString formatters that you have available in any .NET application. No gimmicks. The leading curly braces {} are used to escape the remainder of the string.

The ValueConversion attribute on the class I created is used to indicate to development environments and tools what types of inputs and outputs are expected to the function. In this case, it's always a string out, but the in varies.

Here was the result:

A nicely formatted number.

January 11, 2007

Tired of the Apple iPhone yet?

Yeah. Me too.

Here's a reality check for those of you ready to buy it: "Thoughts on the macworld keynote."

January 3, 2007

Dependency Property Sets and duplicates ...

The question of the day:

"How do I get all of the dependency property sets, regardless of whether or not they might be equal to the previous value?"

Here's one way.

If you have a DependencyProperty, say it's called CPULoad in a class called MonitorControl (a graph of some sort):

public static readonly DependencyProperty CPULoadProperty =
DependencyProperty.Register("CPULoad", typeof(double),
typeof(MonitorControl),
new FrameworkPropertyMetadata(0.0,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(CPULoadChangedHandler)));

Your host application sets the CPULoad property routinely as normal operation (either via the CLR property CPULoad or a SetValue(MontiorControl.CPULoadProperty,{value}). But in this CPULoad property, you actually want to save values over time to graph a history, so you need to record each instance of the property as it is set. By design, DependencyProperties do not call the PropertyChangedCallback method unless the value has actually changed (makes sense, and prevents some unnecessary coding by the developer). So, in this example, the CPULoad DP callback would only get called if the CPULoad changed. That would mean the graph wouldn't update.

So, here's the cheating workaround I discovered:

public static readonly DependencyProperty CPULoadProperty =
DependencyProperty.Register("CPULoad", typeof(double),
typeof(MonitorControl),
new FrameworkPropertyMetadata(0.0,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(CPULoadChangedHandler),
new CoerceValueCallback(CPULoadCoerceHandler)
));

Add the CoerceValueCallback to the FrameworkPropertyMetadata constructor. Although designed to "coerce" a value, it can be used for other devious means as you'll see:

private static object CPULoadCoerceHandler(DependencyObject d,
object baseValue)
{
MonitorControl mc = d as MonitorControl;
if (mc != null && baseValue != null)
{
mc.CPULoadChanged((double)baseValue);
}
return baseValue;
}

In this case, the CPULoadCoerceHandler is called everytime the CPULoad DP property is set! So, within that method, the code casts to the MonitorControl class (the host class for the DP in this instance), and then calls the CPULoadChanged method (which is the same method that the CPULoadChangedHandler calls when the property actually changes (via the PropertyChangedCallback). The baseValue isn't changed at all -- it's just returned unchanged. A hack for doubles that works if you don't mind a slight loss in precision would be to tweak the baseValue ever so slightly as it comes through if the value was the same as the previous value.

It's sneaky, but effective. I'd bet someone Microsoft slaps their forehead in disgust after seeing this!

January 1, 2007

Broken: RichTextBoxes with embedded UIElements

If you've been using the BlockUIContainer or InlineUIContainer elements in a FlowDocument, embedded within a RichTextBox, AND want the embedded UIElements to be enabled in V1 of WPF/.NET 3.0, give up now.

<RichTextBox>
<FlowDocument>
<Paragraph>
<Run>
Here is some text in a paragraph
</Run>
</Paragraph>
<BlockUIContainer IsEnabled="True">
<Button Click="BlockUIClick" IsEnabled="True">Click me please!</Button>
</BlockUIContainer>
<Paragraph>
<Run>
Here is some text in a paragraph after a control.
</Run>
</Paragraph>
<Paragraph>
<Run>
Here is some text in a paragraph
</Run>
<InlineUIContainer>
<Button Click="InlineUIClick">Click me!</Button>
</InlineUIContainer>
</Paragraph>
</FlowDocument>
</RichTextBox>


As of the released version of WPF/.NET 3.0, this functionality no longer works at all if you want the control to actually be enabled -- and appears to be intentional/as expected. Several Microsoft employees have stated as much in forum posts.

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=389348&SiteID=1
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=716043&SiteID=1 (look for a post by Prajakta Joshi)

I'm disappointed to say the least that this wasn't included in the final V1 release, as it's something the company I work for would absolutely have used. I really hope it returns in V2.

Here are some links to a couple of other pages where this was documented as working:

WiredPrairie

Charles Petzold

Help support my web site by searching and buying through Amazon.com (in assocation with Amazon.com).