Miguel A. Castro's Blog

# Sunday, January 23, 2011

I’ve been beating my head against the wall with a small problem and after lots of trial and error, and some searching around, I found the answer – and it was NOT obvious !

The problem space was simple and common:

I have placed a lot of my styles, data templates, and converters into separate XAML files (ResourceDictionaries), with the idea of just declaring them in the App.Xaml since I’m pretty much using these all over the application.  I started out declaring things in my App.Xaml like this:

 

<Application.Resources>
    <controls:BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter" />
    <refractionConverter:ReverseBooleanToVisibilityConverter x:Key="reverseBooleanToVisibilityConverter" />
    <refractionConverter:PercentageConverter x:Key="percentageConverter" />
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary>
            </ResourceDictionary>
            <ResourceDictionary Source="Resources/SharedResources.xaml" />
            <ResourceDictionary Source="Resources/MdiTemplates.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

 

Looks simple enough right?  Wrong.

The second I moved the converters from the individual User Controls to the App.Xaml, styles that are declared in the SharedResources.xaml file became unreachable.  In my debugging efforts, I tapped into the Application_Startup event to examine the resources that WPF was loading.  I noticed that the value of this.Resources.Count was 4, but the value of this.Resources.MergedDictionaries.Count was 0

So I continued digging and here’s what I found out:

Once you commit to using MergedDictionaries in your App.Xaml, you must use them for everything.  I moved the three converters inside the MergedDictionaries section and tested the debugging again.  This time, this.Resources.Count was set to 0, and this.Resources.MergedDictionaries.Count was set to 3.  This told me the resources I listed as MergedDictionaries were now loading.  The key indicator that things were better is that my styles now worked.

Here’s the code:

 

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary>
                <controls:BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter" />
                <refractionConverter:ReverseBooleanToVisibilityConverter x:Key="reverseBooleanToVisibilityConverter" />
                <refractionConverter:PercentageConverter x:Key="percentageConverter" />
            </ResourceDictionary>
            <ResourceDictionary Source="Resources/SharedResources.xaml" />
            <ResourceDictionary Source="Resources/MdiTemplates.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

 

I hope this helps since believe it or not, though there’s a lot of code samples out there, much of it is unclear and flat out wrong.

Until next time…

Sunday, January 23, 2011 3:24:02 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] - - Follow me on Twitter
Dev Stuff | WPF
# Friday, September 24, 2010

One of my latest challenges involved the topic of WPF sizing.  I’m referring to the manipulation of control widths in relation to their parent widths.  Most books tell you that this happens automatically, but I’m here to say otherwise gentle reader.  Now, I am fully prepared to admit that I may have missed something so if you’re a WPF expert out there and you think there is a better way to do what I’m going to show you, please respond. 

So here is my situation:

I have a window (user control actually, but then almost everything is in most WPF applications).  My window defines a grid with two columns, each which will contain a user control of their own.  It looked something like this:

 

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="300" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <src:Control1 Grid.Column="0" x:Name="ctlControl1" DataContext="{Binding Path=Control1ViewModel}" />
    <src:Control2 Grid.Column="2" x:Name="ctlControl2" />
</Grid>

The user control called Control1 contains a ListView control which defined an ItemTemplate to construct the content structure of each item.  The effect I wanted was to show the data for each item in read-only mode and provide an “edit” icon on each item.  Pressing it would convert that item to editable textboxes.  I got that working fine by providing two Grids inside my data template, setting one of them invisible, then using commanding on the button icons to change the view-model state to switch the Visibility of each grid was bound – worked like a charm and is a really nice effect.

My problem was that both the TextBlock and TextBox controls in each item view sized to its contents, then the widest one became the width of the actual ListView that contained everything.  Originally, the column that contained this entire user control (defined above) was set to Auto, so of course it would widen to the width of the longest text control.  This totally sucked.

I added a splitter to the grid in the user control that contained the other two controls and the results at first were weird.  I took the recommendation of most books and added the GridSplitter to its own column (set to Auto width) at first.  This enabled me to resize the grid columns but by then, the content user control’s width was already set and it would not resize along with my resize-drag.  Dragging the splitter to the right, left the contents of the first column fixed, and dragging to the left basically didn’t work past the already fixed-width of the first column.  I fixed this by adding the GridSplitter as the last control on the same column as one of the controls, like this:

 

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="300" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <src:Control1Grid.Column="0" x:Name="ctlControl1" DataContext="{Binding Path=Control1ViewModel}" />
    <GridSplitter ResizeDirection="Columns" Grid.Column="0" Width="3" />
    <src:Control2 Grid.Column="2" x:Name="ctlControl2" />
</Grid>

 

Now when I drag the splitter, the columns follow along the resize in both directions; however the TextBlock and TextBox controls in my ListView remain sized to their contents and would not stretch in either direction.  This still totally sucked.

After playing around significantly with this, I discovered that the ListView itself was sizing and resizing dynamically as I dragged my splitter so I needed to get the contents of each item to size similarly.  The solution was to bind the Width of each of my TextBlock and TextBox items to the width of the ListView in which they were contained.  I gave the ListView control a name (I don’t normally name my controls since most of my work happens in the View-Model anyway), then set the Width property of the contained controls like this:

 

<ListView Name="lvw1" ItemsSource="{Binding}" DataContext="{Binding Path=VuewModelPropertyGoesHere}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid Visibility="{Binding Path=EditMode, Converter={StaticResource notEditModeVisibilityConverter}, Mode=OneWay}">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="25" />
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="25" />
                        <RowDefinition Height="10" />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Path=Name}" TextWrapping="Wrap" HorizontalAlignment="Stretch"
Width="{Binding ElementName=lvw1, Path=ActualWidth}" Margin="0,0,5,0" /> <TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding Path=Description}" TextWrapping="Wrap" HorizontalAlignment="Stretch"
Width="{Binding ElementName=lvw1, Path=ActualWidth}" Margin="0,0,5,0" /> . . . </Grid> <Grid Visibility="{Binding Path=EditMode, Converter={StaticResource editModeVisibilityConverter}, Mode=OneWay}"> <Grid.RowDefinitions> <RowDefinition Height="25" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="10" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <TextBox Grid.Row="0" Grid.Column="0" Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}"
Width="{Binding ElementName=lvw1, Path=ActualWidth, Mode=OneWay}" Margin="0,0,5,0" VerticalAlignment="Center" /> <TextBox Grid.Row="1" Grid.Column="0" Text="{Binding Path=Description, UpdateSourceTrigger=PropertyChanged}"
TextWrapping="Wrap" AcceptsReturn="True" MaxLines="3" MinLines="3" VerticalScrollBarVisibility="Auto"
Width="{Binding ElementName=lvw1, Path=ActualWidth, Mode=OneWay}" Margin="0,0,5,0" VerticalAlignment="Center" />

 

This totally fixed my problem… almost.  While the binding worked perfectly, the look I got left one little detail to be desired.  You see, now the width of the textboxes as equal to the width of the ListView.  This made their right edge disappear, and no margin and padding setting would fix it.

What I needed to do is set the width of the controls to slightly less than the width of the ListView; 10 pixels less to be exact.  Apparently in WPF, you either bind or you do not bind; not a combination of the two.  And you certainly cannot do mathematics in the XAML.  So what to do?

At this point, I had become quite comfortable with the idea of a type-converter so this is the first thing that sprung into my mind.  I created a type converter that I could use in this binding.  The job of the type converter would be to take the incoming width, and rather than convert it to another type, simply adjust by a certain amount and return that number.  And what amount you ask?  Well, the type converter gives you a “parameter’ argument than you can fill in the XAML, so the answer is: any amount I desire.

The end-result worked and looked great.  Here’s the type converter code:

 

public class WidthToParentConverter : IValueConverter
{
    object IValueConverter.Convert(object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    {
        double width = (double)value;
        double adjustment = Convert.ToDouble(parameter);

        return width + adjustment;
    }

    object IValueConverter.ConvertBack(object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    {
        double width = (double)value;
        double adjustment = Convert.ToDouble(parameter);

        return width - adjustment;
    }
}

 

And here’s the adjusted XAML for the textboxes:

 

<TextBox Grid.Row="0" Grid.Column="0" Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}"
Width="{Binding ElementName=lvw1, Path=ActualWidth, Converter={StaticResource widthToParentConverter}, ConverterParameter=-10, Mode=OneWay}"
Margin="0,0,5,0" VerticalAlignment="Center" /> <TextBox Grid.Row="1" Grid.Column="0" Text="{Binding Path=Description, UpdateSourceTrigger=PropertyChanged}"
TextWrapping="Wrap" AcceptsReturn="True" MaxLines="3" MinLines="3" VerticalScrollBarVisibility="Auto"
Width="{Binding ElementName=lvw1, Path=ActualWidth, Converter={StaticResource widthToParentConverter}, ConverterParameter=-10, Mode=OneWay}"
Margin="0,0,5,0" VerticalAlignment="Center" />

 

 

I struggled with this for a quite a bit, mainly because of inexperience, but now I know exactly what to do in this situation and I hope sharing it helps some of you as well.

 

Until Next Time…

Friday, September 24, 2010 4:38:46 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] - - Follow me on Twitter
Dev Stuff | WPF
Search
Me & My Flair

Read all about me here.
Download my Resume here.

Check out where I am here.
 
Click on logos above for profiles.
Archive
<May 2013>
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
Statistics
Total Posts: 40
This Year: 0
This Month: 0
This Week: 0
Comments: 93