Need your help. I have a ListBox (with virtualization) which displays a ScrollViewer.
My ListBox items are expandable, and while expanded their hight may exceed the visible scrolling area.
The problem i'm expiriencing is that when the list box item is exceeds the visible scrolling area - scrolling jumps to the next ListBox item rather than simply scrolling the view.
Check this code:
<ListBox Grid.Row="1" Grid.Column="0" DataContext="{Binding SpecPackageSpecGroupListViewModel}" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling"
ItemContainerStyle="{StaticResource SpecPackageSpecGroupListBoxStyle}" ScrollViewer.IsDeferredScrollingEnabled="True"
ItemsSource="{Binding SortedChildren}" ScrollViewer.CanContentScroll="True"
Background="Transparent"
BorderThickness="0" SelectionMode="Extended"
Margin="5,5,5,5">
<ListBox.ItemTemplate>
<DataTemplate>
<Controls:SpecPackageSpecGroupControl/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Of-course, i can't wrap my ListBox with another scroller since it will turn off the visualization (which is very impotent to me).
If i set CanContentScroll to False everything works as expected - but the virtualization stops working.
HELP!!!
Gili
Ok, so just before i was about to give up and somehow learn how to live with this bug i bumped into a post (which i can't seem to find now) that suggests that TreeView does support Pixel-Based scrolling (AKA Physical Scrolling) without turning off visualization.
So i tried this and indeed - it works! Made sure to verify that virtualization works, tested with ~1000 items and also set a break point on my control constructor and made sure it is called when my view is scrolled.
The only disadvantage of using TreeView instead of ListBox is that TreeView doesn't seem to support multiple item selection (which i needed) - but implementing this is way much easier than implementing the smart scrolling for ListBox.
I created a style for TreeViewItem that makes the TreeViewItem look and behave just like ListBoxItem, this is really not mandatory - but i preferred it like this (beside the fact that the basic style has stretching issues which i had to fix with styling). Basically i removed the ItemsPresenter and stayed only with the ContentPresenter since my data is not hierarchical:
<Style x:Key="MyTreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Border Name="myBorder"
SnapsToDevicePixels="true"
CornerRadius="0,0,0,0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
BorderThickness="0"
BorderBrush="Transparent"
Height="Auto"
Margin="1,1,1,3"
Background="Transparent">
<ContentPresenter Grid.Column="1" x:Name="PART_Header" HorizontalAlignment="Stretch" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" ContentSource="Header"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now - the only thing i've got left to do is implement the multi-selection tree view.
There might be different approaches to implement such behavior, i took the ViewModel approach.
Derived from TreeView i created a new MultiSelectionTreeView:
public class MultiSelectionTreeView : TreeView
{
private static bool CtrlPressed
{
get
{
return Keyboard.IsKeyDown(Key.LeftCtrl);
}
}
protected override void OnSelectedItemChanged(RoutedPropertyChangedEventArgs<object> e)
{
base.OnSelectedItemChanged(e);
var previouseItemViewModel = e.OldValue as IMultiSelectionTreeViewItemViewModel;
if (previouseItemViewModel != null)
{
if (!CtrlPressed)
previouseItemViewModel.IsSelected = false;
}
var newItemViewModel = e.NewValue as IMultiSelectionTreeViewItemViewModel;
if (newItemViewModel != null)
{
if (!CtrlPressed)
newItemViewModel.ClearSelectedSiblings();
newItemViewModel.IsSelected = true;
}
}
}
Where IMultiSelectionTreeViewItemViewModel is as follows:
public interface IMultiSelectionTreeViewItemViewModel
{
bool IsSelected { get; set; }
void ClearSelectedSiblings();
}
Of course - now it is my responsibility to handle the way selected items are being presented - in my case it was given since my tree view items had their own DataTemplate which had indication for its selection.
If this is not your case and you need it, simply extent your tree view item data template to indicate its selection state according to its view model IsSelected property.
Hope this will help someone someday :-)
Have fun!
Gili
This question is still coming up in search engines, so I'll answer it 2 years later.
WPF 4.5 now supports pixel based virtualizing panels.
If you can target 4.5, then just add this to your ListBox:
<Setter Property="VirtualizingPanel.ScrollUnit" Value="Pixel"/>
For what's new in .NET 4.5 (which includes new stuff in WPF 4.5) see http://msdn.microsoft.com/en-us/library/ms171868.aspx
Take a look here (Bea Stollnitz) and here(Dan Crevier); basically you'll need to implement your own container that supports both virtualization and pixel-based scrolling. You can also look at this similar SO question for more details or a possible alternative. Deriving from VirtualizingStackPanel and modifying the scroll behavior seems to be the best bet.
Related
I have a visio-like interfact but have actual model data behind some of the elements. The elements can be moved by the user.
I use a contentcontrol on a canvas whereby the viewmodels of the elements are places in the content which can then be displayed differently depending on their type but using the same contentcontrol. It is simple to bind the view to the different properties in the viewmodel. However, I have to save the position in the model, and I cannot find a binding solution.
1) The Application.Save Command is handled in the main view model, so I do not have access to the view there. That means I must save the postion data when the elements are moved, or is there a better approach?
2) Assuming that I am right with 1), I am looking to avoid code behind, i.e. I do not want the contentcontrol to deal with the elements that they have in their content. However, so far the code behind version is all I could come up with:
My code behind solution so far:
All model elements implement an interface:
public interface IViewElement
{
String Position { get; set; }
}
And in the contentcontrol:
void ContentControl_LayoutUpdated(object sender, EventArgs e)
{
IViewElement content = this.Content as IViewElement;
content.Position = new Point(Diagram.GetLeft(this), Diagram.GetTop(this)).ToString();
}
The XAML:
<Style TargetType="{x:Type diagram:Item}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type diagram:Item}">
<Grid Canvas.Top="{Binding ElementName=PART_ContentPresenter, Path=Content.Position, Mode=TwoWay}" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"
ContextMenu="{x:Null}">
<!-- PART_ContentPresenter -->
<ContentPresenter x:Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<DataTemplate DataType="{x:Type local:ViewModel}">
<StackPanel>
...
</StackPanel>
Just encapsulate the codebehind you've used in a Behavior
Why are you using a string to store the position? Use either a Point or two decimal values, and then bind your ContentControl's Canvas.Top and Canvas.Left position to these values using two-way binding.
It will automatically update the model when the Top and Left positions change.
Edit:
Here's an example:
<ContentControl Canvas.Top="{Binding ContentModel.Top, Mode=TwoWay}"
Canvas.Left="{Binding ContentModel.Left, Mode=TwoWay}"
Content="{Binding ContentModel}" />
Imagine a form designer with a grid overlay that would represent coordinates on a plane. I'm trying to bind the properties of the grid overlay to the Canvas within a custom ItemsControl.
The grid is created using a VisualBrush. The VisualBrush's Viewbox and Viewport are bound to a Rect in the code, as well are the Height and Width of the Rectangle used to display the grid tile. However, when the control displays, the grid tiles seem to be "infinitely small" (the grid is just grey) in that if I zoom into the grid, the program will eventually just seize up, unable to render it. Obviously, this is not the effect I'm going for.
<Style TargetType="{x:Type Controls:FormControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Controls:FormControl}">
<Border Background="White"
BorderBrush="Black"
BorderThickness="1"
Padding="49">
<Grid Height="{TemplateBinding CanvasHeight}"
Width="{TemplateBinding CanvasWidth}">
<Grid.Background>
<!--"0,0,6,11.3266666666667"-->
<VisualBrush TileMode="Tile"
Viewbox="{TemplateBinding GridUnitViewbox}"
ViewboxUnits="Absolute"
Viewport="{TemplateBinding GridUnitViewbox}"
ViewportUnits="Absolute">
<VisualBrush.Visual>
<Rectangle Height="{Binding RelativeSource={RelativeSource TemplatedParent},Path=GridUnitViewbox.Height}"
HorizontalAlignment="Left"
Opacity="{TemplateBinding GridOpacity}"
Stroke="Black"
StrokeThickness=".1"
VerticalAlignment="Top"
Width="{Binding RelativeSource={RelativeSource TemplatedParent},Path=GridUnitViewbox.Width}" />
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>
<ItemsPresenter />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="Controls:FormControlItem">
<Setter Property="Canvas.Left"
Value="{Binding Path=X}" />
<Setter Property="Canvas.Top"
Value="{Binding Path=Y}" />
</Style>
</Setter.Value>
</Setter>
</Style>
Any idea what I am doing wrong here. Thanks.
EDIT:
Maybe a little more background of what I'm doing may put it in better context. I work for a tax preparation software company and, currently, our forms division creates substitute forms using a markup that was written for our product like a million years ago. It's a bit cumbersome creating forms this way, so I'm developing a visual "Form Designer" for them that will be more like a WYSIWYG and translate the contents of the designer into markup. Well, the IRS is real anal about everything on the form being EXACTLY where it was on the original, so there is a very loose standard by which a "grid overlay" can be placed over the form to determine where things need to go; basically a coordinate plane of sorts.
FormControl is essentially the visual representation of one of these substitute forms that one of the forms designers would be creating.
CanvasWidth and CanvasHeight are CLR wrappers to Dependency Properties. They are assigned values in OnApplyTemplate() by multiplying the dimensions of GridUnitViewbox by however many grid tiles need to be in the grid overlay, ie. a 78x63 grid in most cases.
The names CanvasWidth and CanvasHeight I think might be a little misleading in that they do not refer to the Canvas control, but to the Grid that houses the Canvas (probably need to change the naming convention). That said, CanvasHeight, CanvasWidth and GridUnitViewbox are not dependent on any control's properties, but rather calculations that are done in OnApplyTemplate().
public static readonly DependencyProperty GridUnitViewboxProperty =
DependencyProperty.Register("GridUnitViewbox", typeof(Rect), typeof(FormControl),
new FrameworkPropertyMetadata(new Rect(0, 0, 6, 11.3266666666667),
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsParentMeasure));
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
FormattedText formattedText = new FormattedText(
"X",
CultureInfo.GetCultureInfo("en-us"),
FlowDirection.LeftToRight,
new Typeface("Courier New"),
10,
Brushes.Black);
this.GridUnitViewbox = new Rect(0, 0, formattedText.WidthIncludingTrailingWhitespace, formattedText.Height);
if (this.PageLayout == PageLayoutType.Landscape)
{
this.CanvasHeight = this.GridUnitViewbox.Height * 48.0;
this.CanvasWidth = this.GridUnitViewbox.Width * 109.0;
}
if (this.PageLayout == PageLayoutType.Portrait)
{
this.CanvasHeight = this.GridUnitViewbox.Height * 63.0;
this.CanvasWidth = this.GridUnitViewbox.Width * 78.0;
}
}
The Grid control is actually doing exactly what I want it to do. It is the VisualBrush and the Rectangle within that are either not binding correctly or are not being updated properly. The comment right below <Grid.Background> was the hard-coded testing value that I was using for VisualBrush's Viewbox and Viewport as well as Rectangle's dimensions before I got around to binding the values and it produced, visually, exactly what I was going for. I've also confirmed that 6 and 11.3266666666667 are, in fact, the values for the GridUnitViewbox's dimensions during runtime.
I have a feeling that the binding is producing '0,0,0,0' however, because the grid overlay is just a grey shading that is eating up an immense amount of resources; locks the program, in fact, if you zoom into it. As you can see, in the code I tried adding AffectsMeasure and AffectsParentMeasure to the Metadata options of the dependency property in hopes that perhaps the UI was not updating properly after GridUnitViewbox's dimensions were updated, but I was wrong. I'm not sure what else it could be.
Alright, just in case anyone else encounters a similar issue, I found the workaround. Apparently VisualBrush is a little finicky about TemplateBindings. I adjusted the XAML thusly and it solved the problem:
<VisualBrush TileMode="Tile"
Viewbox="{Binding RelativeSource={RelativeSource TemplatedParent},Path=GridUnitViewbox}"
ViewboxUnits="Absolute"
Viewport="{Binding RelativeSource={RelativeSource TemplatedParent},Path=GridUnitViewbox}"
ViewportUnits="Absolute">
Here is the article where I got the information from.
What is CanvasWidth and CanvasHeight? Are they defined?
Also, I believe that a canvas has no size unless explicitly specified. When you bind to it, the width/height may be zero.
Try ActualWidth and ActualHeight or the canvas after the rendering pass to see if they contain any values, but afaik they don't unless you provide one.
I have a problem with the UI virtualization of an ListView with hundreds of elements which items can expose an Visibility property.
Unfortunately the virtualization not recognizes the visibility correct and makes the Scrollbar smaller or bigger according to how many items are in the ViewPort (and not how many items are non-collapsed in the ViewPort) at the scrolled position.
Is there any way to avoid this problematic without turning off virtualization?
Please see example attached:
<ListView VerticalAlignment="Stretch"
Name="ListViewControl"
HorizontalAlignment="Stretch"
ItemsSource="{Binding Movies}"
ScrollViewer.VerticalScrollBarVisibility="Visible"
SelectedItem="{Binding MovieSelected, Mode=OneWayToSource}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="MouseDoubleClick" Handler="ItemClicked" />
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Visibility" Value="{Binding Visibility}"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The point of virtualization is that the UI can render without having to examine every item in the collection, right? But the result you want is dependent on doing just that - the scrollbar can't accurately calculate the size of the thumb without knowing how many items are visible, and in order to do that it has to look at each item. When you have virtualization turned on, the scrollbar doesn't look at every item, it just uses the number of items in the collection as an estimate.
What might work better, instead of collapsing items that you don't want to see, is to remove them from the items source. Use a CollectionView and filter out items whose Visibility is Visibility.Collapsed. You still incur the cost of visiting each item in the base collection to determine if it should appear in the view, but it will probably be quite a bit faster if that's done upstream of the items in the collection actually being rendered in the UI.
I'm having a heck of a time trying to template bind the StrokeThickness of a rectangle.
My goal is to allow a user of my custom control to set a property called SelectedBorderThickness which will, in fact, set the StrokeThickness of a rectangle.
I thought I understood templating but I guess I really don't.
If I do this:
<Rectangle x:Name="myRect" Height="100" Width="100" Stroke="Black" SelectedBorderThickness="5" />
Can someone please show me how to write the Style elements to get this to work?
You should add more details to the question and people will be able to help you more easily. I think I have figured out what you want though.
You are looking to make a custom templated silverlight control, containing a bunch of elements incluiding a rectangle in its template. You would like a user to be able to set the thickness of that rectangle inside the control with a property on the control itself. From what you put above, I don't know how much you have written in your code -- so I will just post a nearly complete example of what you are after.
First I created a templated custom control in visual studio, and added the new dependancy property we want a user to be able to set:
public class TestControl : Control
{
static public DependencyProperty SBTProperty { get; set; }
static TestControl()
{
SBTProperty = DependencyProperty.Register("SelectedBorderThickness", typeof(double), typeof(TestControl),null);
}
public TestControl()
{
this.DefaultStyleKey = typeof(TestControl);
}
public double SelectedBorderThickness
{
get { return (double)GetValue(SBTProperty); }
set { SetValue(SBTProperty, value); }
}
}
Then I set up the template in Generic.xaml (for my example the only thing I have in my control is the rectangle since I don't know what you want in there):
<Style TargetType="local:TestControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TestControl">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Rectangle Fill="Bisque" Stroke="Black" StrokeThickness="{TemplateBinding SelectedBorderThickness}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now I am all set up to use it from xaml in other parts of my application. For my example, I put one right in the center of my MainPage:
<local:TestControl SelectedBorderThickness="75"></local:TestControl>
EDIT:
After reading your code below, I see now what the problem is. You're trying to do a template binding, but the way you have it it's going to try to bind to the current template, which is the template for listboxitem and not your custom listbox. What you really want in this situation is to do a RelativeBinding with FindAncestor to jump up the tree to the template of your custom listbox, but MS hasn't yet implemented that kind of binding in Silverlight (even though it's pretty common in WPF). Luckily in your specific situation we can finagle the right object through the path in a TemplatedParent binding, without having to write a bunch of messy codebehind to emulate an ancestor binding:
StrokeThickness="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content.Parent.SelectedBorderThickness}"
Dump that into the Rectangle in your template posted above and it should work -- it will access the content of the ListBoxItem (which is whatever you are displaying), and then access that objects Parent (which will be your custom listbox). From there we just hit up the property we set up before.
If you want a cleaner solution, join the chorus of us asking MS to implement ancestor binding in Silverlight.
Here's the problem section, it's when I'm attempting to style the ItemContainerStyle for my custom control which derives from a ListBox:
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid Background="{TemplateBinding Background}">
<!-- VSM stuff removed for clarity -->
<ContentPresenter
x:Name="contentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"/>
<Rectangle x:Name="FocusVisualElement"
Stroke="Goldenrod"
StrokeThickness="{TemplateBinding SelectedBorderThickness}"
Visibility="Collapsed"
RadiusX="1"
RadiusY="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
The problems is that when I set StrokeThickness = {TemplateBinding SelectedBorderThickness} on the Rectangle and then try to use the control in a test app, I get a ParserError:
Message: Unknown attribute StrokeThickness on element Rectangle
If I hardcode the StrokeThickness to 3 (or whatever), it parses fine and I can view the test app.
In the end, all I'm really trying to do is create a property that shows up in Intellisense so that my (eventual) end users of my custom control can change the color and border thickness, radius, etc. of the highlight on a hovered and selected ListBoxItem in a dynamically bound custom ListBox. It shouldn't be this dang hard.
The dang comments are too restricted. I'm not trying to answer my own question (I wish I could).
David, your code works fine when you add ListBoxItems statically. When adding them dynamically, the thickness doesn't change. To test this out, I added a new TestControl in MainPage:
<StackPanel>
<local:TestControl SelectedBorderThickness="9" x:Name="h1n1">
<TextBlock Text="Honk1"></TextBlock>
<TextBlock Text="Honk2"/>
</local:TestControl>
<local:TestControl x:Name="SwineFlu" SelectedBorderThickness="20" />
</StackPanel>
In the code-behind I added:
ObservableCollection<string> test = new ObservableCollection<string>();
test.Add("Hi David");
test.Add("Hello World");
SwineFlu.ItemsSource = test;
I have this case
<WrapPanel>
<CheckBox>Really long name</CheckBox>
<CheckBox>Short</CheckBox>
<CheckBox>Longer again</CheckBox>
<CheckBox>Foo</CheckBox>
<Slider MinWidth="200" />
</WrapPanel>
I want all the CheckBoxes inside the WrapPanel to be the same width.
Adding the following almost accomplishes the desired effect
<WrapPanel.Resources>
<Style TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
<Setter Property="MinWidth" Value="75" />
</Style>
</WrapPanel.Resources>
However, I do not want to hardcode a specific width, rather let the largest CheckBox set the width (the above also fails if any width > 75).
The Slider is independent and should be allowed to be larger than the CheckBoxes.
I do not want to use a Grid (with IsSharedSizeScope) since I do not want a hardcoded number of columns.
This article presents an interesting solution, but it would be nice to solve the problem without creating a custom control or using C# code.
What is the best way to do this, preferrably in XAML only?
I originally looked at this using IsSharedSizeGroup but hit a roadblock with making it dynamically apply to things instead of explicitly wrapping items. In this case, creating an AttachedProperty in code or another code based solution may in the long run be better then a XAML only approach. However, to create a purely XAML solution we can make use of the SharedSizeGroup property on a ColumnDefinition to share the sizes of each element and then use set the IsSharedSizeScope property on the WrapPanel. Doing so will make all of the contents in the WrapPanel with the same SharedSizeGroup share their width for columns and height for rows. To wrap the ComboBoxes and possibly ComboBoxes that are not currently in the XAML but will be added to the WrapPanel, we can create a Style and re-template the ComboBox to bassicly wrap it with a Grid.
<WrapPanel Grid.IsSharedSizeScope="True">
<WrapPanel.Resources>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid Background="LightBlue">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="WrapPannelGroup" />
</Grid.ColumnDefinitions>
<CheckBox Style="{x:Null}"
IsChecked="{TemplateBinding IsChecked}">
<!--Other TemplateBindings-->
<CheckBox.Content>
<ContentPresenter />
</CheckBox.Content>
</CheckBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</WrapPanel.Resources>
<CheckBox>Really long name</CheckBox>
<CheckBox>Short</CheckBox>
<CheckBox IsChecked="True">Longer again</CheckBox>
<CheckBox>Foo</CheckBox>
<Slider MinWidth="200" />
</WrapPanel>
Here we are re-templating all CheckBoxes without a style inside the WrapPannel to instead be CheckBoxes surrounded by a Grid. However, because of this we need to re-bind all of the CheckBoxes properties that we want to maintain. While that could become burdensome, it also allows for a pure XAML approach.
You can add a property or a converter that does the needed work, then bind each column's width to it. The property or converter can access the entire list of items, finding the widest one, and returning the desired width for all elements.
The best way to do this is to use a CustomControl like the article you posted.
Any solution you come across is going to have to iterate through the list of items and find the maximum width during the measure phase.
Any sort of XAML-only answer would have to be provided OOTB (e.g. IsSharedSizeScope), or would leverage some sort of multi-binding to link the items together. Thus any sort of XAML answer would be full of markup which makes it more verbose (and less elegant).
The only modification that I see to the CodeProject article you posted is adding the ability to "turn-off" consideration of certain elements (like your slider). This could be done as an additional attached property.