|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionI can assure you that this article was very difficult for me to write. This is my thirtieth article on CodeProject, so I thought it was time for a new challenge. The challenge is not only for me, but for you, the reader, as well. Hopefully I have overcome the challenge of writing this article well enough so that you might now face the far more daunting challenge of learning how to think in WPF. If you are already a seasoned WPF developer, feel free to read on and see how someone else solves problems using WPF. This article attempts to explain the thought processes I went through while designing and implementing a solution to a problem using WPF. If you are fairly new to WPF and are just starting to ascend its infamous learning cliff, perhaps this article will help to shed some light on why and how one might put many of the WPF concepts to use. I am not claiming that my way of approaching WPF is the "right" or "best" way, but simply the way I think about it. BackgroundThis article assumes that you are already somewhat familiar with WPF. We will not be covering the basics here. If you need to learn about the fundamentals of WPF, you might want to check out my five-part Guided Tour of WPF right here on CodeProject. The Problem to be SolvedI wanted to create sleek-looking selection indicators for the items in a
…I wanted it to look something like this instead…
In both of those screenshots above, the same three items are selected in the The selection indicators do not need to be interactive. If the user clicks on an indicator, nothing should happen. It is only a visual feature, and does not affect the state of the I also want the "selection indicator" functionality to be reusable, so that I can easily apply selection indicators to any Where to Start?At this point we have a pretty clear understanding of the problem which needs to be solved. Now it is time to compare and contrast various possible solutions to the problem. A few approaches come to mind, so let's review them.
We just reviewed three possible approaches to how and where the selection indicators will be rendered. None of them worked out, but we learned three important points along the way. Those points are:
The third point needs some clarification. We can only provide support for so much customization to the Based on all of the points introduced above, we must now decide how to move forward and start implementing this feature. We can satisfy all of our constraints by creating a <UserControl>
<DockPanel>
<Grid DockPanel.Dock="Left" />
<ListBox />
</DockPanel>
</UserControl>
How to Draw the Selection Indicators?The selection indicators are not part of the Welcome back. If you took some time to contemplate how the selection indicators should be managed, you probably realized that there are many ways to skin that cat. If your first instinct was to owner-draw little triangles next to the selected items, you should take a look around you and realize that you aren't in Kansas anymore. WPF certainly allows you to do low-level rendering, somewhat similar to working with an HDC or One seemingly viable approach would be to hook the That technique would certainly work, but it just doesn't feel "right" to me. In my opinion we should not be manually creating and positioning the selection indicators. They should create and position themselves, based purely on some XAML markup. This reduces the number of moving parts in our code, which means that there will be fewer bugs to fix. So how can we implement this logic without writing too much code? The solution to this problem makes use of several powerful features of WPF: an items panel, data binding an attached property, and a A selection indicator has a fixed width and height, and also has a fixed horizontal offset from the left edge of the container in which it lives. The only variable it does not know by itself is its vertical offset from the top of the container in which it lives. That vertical offset effectively determines which selected Suppose we were to calculate the vertical offsets needed to display selection indicators next to each selected
Obviously that is not the visual effect we're after, but it is a start. At this point we have an <DataTemplate>
<Grid Width="16" Height="16">
<!--
A lightweight drop shadow
under the selection indicator.
-->
<Polygon Fill="LightGray">
<Polygon.Points>
<Point X="4" Y="4" />
<Point X="16" Y="10" />
<Point X="4" Y="16" />
</Polygon.Points>
</Polygon>
<!--
The selection indicator itself.
-->
<Polygon Fill="{Binding ElementName=mainControl, Path=IndicatorBrush}">
<Polygon.Points>
<Point X="2" Y="2" />
<Point X="14" Y="8" />
<Point X="2" Y="14" />
</Polygon.Points>
</Polygon>
</Grid>
</DataTemplate>
Once we do that, the UI looks like this:
That certainly doesn't look right! What's the problem here? Why aren't the selection indicators next to the selected items? Take a moment, think about it before continuing. It's OK, I'll wait… The problem here is that our selection indicators have no idea that the By default <!--
Host all of the selection indicators
within a Canvas panel.
-->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!--
Position a selection indicator based on the
offset value to which it is bound.
-->
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Top" Value="{Binding Path=.}" />
</Style>
</ItemsControl.ItemContainerStyle>
Since
When are the Selection Indicator Offsets Calculated?The exact details of how the offsets are calculated are not relevant for this discussion, but it is interesting to note when they are calculated. In two situations it is important to update the offsets, when the selected items change and when the items are scrolled. Here is the public ListBoxWithIndicator()
{
InitializeComponent();
// Set up the list of selection indicator offsets
// as the data source for the ItemsControl.
_indicatorOffsets = new ObservableCollection<double>();
_indicatorList.ItemsSource = _indicatorOffsets;
// Move the indicators when the set of
// selected items is modified.
_listBox.SelectionChanged += delegate
{
this.UpdateIndicators();
};
// Move the indicators when the ListBox's
// ScrollViewer is scrolled.
_listBox.AddHandler(
ScrollViewer.ScrollChangedEvent,
new ScrollChangedEventHandler(delegate
{
this.UpdateIndicators();
}));
}
The way that the How to Make the ListBox and Selection Indicators Customizable?So far we have figured out a way to render the selection indicators and keep them up-to-date as the user interacts with the <!-- This is invalid XAML. -->
<local:ListBoxWithIndicator>
<local:ListBoxWithIndicator.ListBox>
<ListBox.ItemsSource>
<SomeData />
</ListBox.ItemsSource>
</local:ListBoxWithIndicator.ListBox>
</local:ListBoxWithIndicator>
The problem is that there's no way to easily access the inner The solution I decided to use is to simply expose a dependency property on <!-- In ListBoxWithIndicator.xaml -->
<ListBox
x:Name="_listBox"
Style="{Binding ElementName=mainControl, Path=ListBoxStyle}"
/>
When you create an instance of the control, you can set its I also created a public dependency property called ConclusionIf you are new to WPF, but have experience with older UI platforms, it is no small feat to unlearn your old way of doing things and learn the WPF way. There are many ways that WPF offers the developer new powers, but you have to be willing to go through the humbling experience of being a newbie all over again. Hopefully this article will help to accelerate that painful process for you, assuming you need any help in the first place. Revision History
| ||||||||||||||||||||