Breadcrumb style with WPF-ListView - wpf

I want to create a simple breadcrumb bar with ListView. Following a simple wireframe screenshot what I would like to archive in the future:
Now, I already created already some code, mainly doing it with DataTemplates, which works actually quite well, but I have some visual problems I am not able to solve:
Main problem concerns the different width of the items. The center of an "arrow" should be stretched and the tail and head should be a fixed width...
The other problem is the visual style of the first and last items
Here's the actual code:
<ListView DockPanel.Dock="Left" ItemsSource="{Binding TagList}"
MinWidth="300" Background="Transparent" BorderThickness="0"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Hidden" Margin="8,0,0,0">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="-8,0,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="8"/>
</Grid.ColumnDefinitions>
<Path Stretch="Fill" StrokeLineJoin="Round" Stroke="#FF000000" Fill="#FFC64242" Data="F1 M 112,144L 104,144L 112,160L 104,176L 112,176" HorizontalAlignment="Stretch" Height="Auto" VerticalAlignment="Stretch" Width="Auto"/>
<Grid HorizontalAlignment="Stretch" Height="Auto" VerticalAlignment="Stretch" Width="Auto" Grid.Column="1">
<Rectangle Stretch="Fill" Fill="#FFC64242" HorizontalAlignment="Stretch" Height="Auto" Margin="0.5" VerticalAlignment="Stretch" Width="Auto"/>
<Path Stretch="Fill" StrokeLineJoin="Round" Stroke="#FF000000" Data="F1 M 128,144L 160,144" HorizontalAlignment="Stretch" Height="1" Margin="0" VerticalAlignment="Top" Width="Auto"/>
<Path Stretch="Fill" StrokeLineJoin="Round" Stroke="#FF000000" Data="F1 M 128,176L 160,176" HorizontalAlignment="Stretch" Height="1" Margin="0" VerticalAlignment="Bottom" Width="Auto"/>
</Grid>
<Path Stretch="Fill" StrokeLineJoin="Round" Stroke="#FF000000" Fill="#FFC64242" Data="F1 M 168,144L 176,160L 168,176" Height="Auto" VerticalAlignment="Center" Width="8" HorizontalAlignment="Right" Grid.Column="2" d:LayoutOverrides="GridBox"/>
<DockPanel LastChildFill="True" Grid.ColumnSpan="2" Grid.Column="1">
<Label DockPanel.Dock="Left" FontSize="12" Content="{Binding Content, FallbackValue=Tagname n/a}" HorizontalAlignment="Left" Grid.Column="0" VerticalAlignment="Center" d:LayoutOverrides="Height" Margin="8,0"/>
<Button DockPanel.Dock="Right" Content="X" Background="Transparent" FontSize="12" Command="{Binding RemoveTagBtn}" Grid.Column="0" Width="13.077" d:LayoutOverrides="Height" VerticalAlignment="Center" Margin="0,0,8,0"/>
<!--<Border Background="#FFf7f7f7" BorderBrush="#FFc9c9c9" BorderThickness="1" CornerRadius="4" HorizontalAlignment="Left" Margin="0,0,0,5.96" d:LayoutOverrides="Height"/> -->
</DockPanel>
</Grid>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Now as I had to find an answer myself in short time, this is my current solution. Also if you do not need the "selectable" feature of ListBox, you can exchange it with ItemControl.
Here's the code. Please be aware that I've commented out the "IsSelected" Triggers for the ItemStyleContainer...
<ListBox Padding="0" DockPanel.Dock="Left" ItemsSource="{Binding TagList}"
MinWidth="300" Background="Transparent" BorderThickness="0"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Hidden">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Margin="8,0,0,0" Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="{DynamicResource LXBarButtonBackgroundNormal}"/>
<Setter Property="BorderBrush" Value="{DynamicResource LXBarButtonBorderNormal}"/>
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<DockPanel LastChildFill="True" Margin="-8,0,0,0">
<Path DockPanel.Dock="Left" Stroke="{DynamicResource LXBarButtonBorderNormal}" Fill="{DynamicResource LXBarButtonBackgroundNormal}" Data="F1 M 112,144L 104,144L 112,160L 104,176L 112,176" Stretch="Fill" Height="32" Width="8" />
<Path DockPanel.Dock="Right" Stroke="{DynamicResource LXBarButtonBorderNormal}" Fill="{DynamicResource LXBarButtonBackgroundNormal}" Data="F1 M 168,144L 176,160L 168,176" Stretch="Fill" Height="32" Width="8" />
<Border Name="Border" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" Padding="{TemplateBinding Padding}" BorderThickness="0,1" VerticalAlignment="Center">
<ContentPresenter />
<!--
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Border" Property="Background"
Value="Blue"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground"
Value="Red"/>
</Trigger>
</ControlTemplate.Triggers>
-->
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel VerticalAlignment="Center" Height="30">
<local:LXImageButton BorderThickness="0" Style="{DynamicResource LXBarImageButton}" Padding="0" DockPanel.Dock="Right" Background="Transparent" Command="{Binding RemoveTagBtn}" Height="16" Width="16"
NormalImage="/com.example.Views;component/Resources/Icons/Buttons/btnCloseXS_normal.png"
ActiveImage="/com.example.Views;component/Resources/Icons/Buttons/btnCloseXS_active.png"
HoverImage="/com.example.Views;component/Resources/Icons/Buttons/btnCloseXS_hover.png"
PressedImage="/com.example.Views;component/Resources/Icons/Buttons/btnCloseXS_hover.png"
DisabledImage="/com.example.Views;component/Resources/Icons/Buttons/btnCloseXS_passive.png"
/>
<Label DockPanel.Dock="Left" FontSize="12" Content="{Binding Content, FallbackValue=Tagname n/a}" VerticalAlignment="Center"/>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

I made a custom shape that renders the arrow that you want.
I used some code from the Rectangle class.
As for the part of styling the first and last elements. You will need some sort of AttachedBehavior that adjusts some property based on the item index.
using System;
using System.Windows.Shapes;
using System.Windows;
using System.Windows.Media;
namespace CustomShapes
{
public class SquaredArrow : Shape
{
protected Rect _rect = Rect.Empty;
#region TipOffset
/// <summary>
/// TipOffset Dependency Property
/// </summary>
public static readonly DependencyProperty TipOffsetProperty =
DependencyProperty.Register("TipOffset", typeof(double), typeof(SquaredArrow),
new FrameworkPropertyMetadata((double)10, FrameworkPropertyMetadataOptions.AffectsRender));
/// <summary>
/// Gets or sets the TipOffset property. This dependency property
/// indicates ....
/// </summary>
[System.ComponentModel.Category("SquaredArrow")]
public double TipOffset
{
get { return (double)GetValue(TipOffsetProperty); }
set { SetValue(TipOffsetProperty, value); }
}
#endregion
public SquaredArrow()
{
Rectangle r = new Rectangle();
r.Measure(new Size(100, 100));
r.Arrange(new Rect(0, 0, 100, 100));
}
static SquaredArrow()
{
StretchProperty.OverrideMetadata(typeof(SquaredArrow), new FrameworkPropertyMetadata(Stretch.Fill));
}
protected override Geometry DefiningGeometry
{
get { return CreateShape(); }
}
/// <summary>
/// Return the transformation applied to the geometry before rendering
/// </summary>
public override Transform GeometryTransform
{
get
{
return Transform.Identity;
}
}
/// <summary>
/// This is where the arrow shape is created.
/// </summary>
/// <returns></returns>
private Geometry CreateShape()
{
double width = _rect.Width;
double height = _rect.Height;
double borderOffset = GetStrokeThickness() / 2d;
PathGeometry g = new PathGeometry();
PathFigure figure = new PathFigure();
figure.IsClosed = true;
figure.StartPoint = new Point(borderOffset, borderOffset);
figure.Segments.Add(new LineSegment(new Point(width - TipOffset + borderOffset, borderOffset), true));
figure.Segments.Add(new LineSegment(new Point(width + borderOffset, height / 2d + borderOffset), true));
figure.Segments.Add(new LineSegment(new Point(width + borderOffset - TipOffset, height + borderOffset), true));
figure.Segments.Add(new LineSegment(new Point(borderOffset, height + borderOffset), true));
g.Figures.Add(figure);
return g;
}
/// <summary>
/// Updates DesiredSize of the Rectangle. Called by parent UIElement. This is the first pass of layout.
/// </summary>
/// <param name="constraint">Constraint size is an "upper limit" that Rectangle should not exceed.</param>
/// <returns>Rectangle's desired size.</returns>
protected override Size MeasureOverride(Size constraint)
{
if (Stretch == Stretch.UniformToFill)
{
double width = constraint.Width;
double height = constraint.Height;
if (Double.IsInfinity(width) && Double.IsInfinity(height))
{
return GetNaturalSize();
}
else if (Double.IsInfinity(width) || Double.IsInfinity(height))
{
width = Math.Min(width, height);
}
else
{
width = Math.Max(width, height);
}
return new Size(width, width);
}
return GetNaturalSize();
}
/// <summary>
/// Returns the final size of the shape and cachnes the bounds.
/// </summary>
protected override Size ArrangeOverride(Size finalSize)
{
// Since we do NOT want the RadiusX and RadiusY to change with the rendering transformation, we
// construct the rectangle to fit finalSize with the appropriate Stretch mode. The rendering
// transformation will thus be the identity.
double penThickness = GetStrokeThickness();
double margin = penThickness / 2;
_rect = new Rect(
margin, // X
margin, // Y
Math.Max(0, finalSize.Width - penThickness), // Width
Math.Max(0, finalSize.Height - penThickness)); // Height
switch (Stretch)
{
case Stretch.None:
// A 0 Rect.Width and Rect.Height rectangle
_rect.Width = _rect.Height = 0;
break;
case Stretch.Fill:
// The most common case: a rectangle that fills the box.
// _rect has already been initialized for that.
break;
case Stretch.Uniform:
// The maximal square that fits in the final box
if (_rect.Width > _rect.Height)
{
_rect.Width = _rect.Height;
}
else // _rect.Width <= _rect.Height
{
_rect.Height = _rect.Width;
}
break;
case Stretch.UniformToFill:
// The minimal square that fills the final box
if (_rect.Width < _rect.Height)
{
_rect.Width = _rect.Height;
}
else // _rect.Width >= _rect.Height
{
_rect.Height = _rect.Width;
}
break;
}
return finalSize;
}
/// <summary>
/// Get the natural size of the geometry that defines this shape
/// </summary>
protected Size GetNaturalSize()
{
double strokeThickness = GetStrokeThickness();
return new Size(strokeThickness, strokeThickness);
}
protected double GetStrokeThickness()
{
return this.StrokeThickness;
}
/// <summary>
/// Render callback.
/// </summary>
protected override void OnRender(DrawingContext drawingContext)
{
Pen pen = new Pen(Stroke, GetStrokeThickness());
drawingContext.DrawGeometry(Fill, pen, DefiningGeometry);
}
}
}

Related

WPF controls not showing even make it visible

I encounter a problem that I am not sure how to fix. I have "AddAccount" (Button), "AccountsTextBlock" and a ListBox on the main window. If not user is added, the list box will be hidden, once user is added, it will show the ListBox, AddAccount and AccountsTextBlock on the same screen, but AddAccount and AccountsTextBlock will be at different positions from when there's no user. The problem is that AddAccount and AccountsTextBlock didn't show, but a dotted rectangle shows.
<Grid>
<Canvas>
<TextBlock x:Name="AddAccountTextBlock" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Top" Height="60" Width="369" Text="Add account to allow for simple access to your company resources
" SnapsToDevicePixels="True" Canvas.Left="38" Canvas.Top="313"/>
<Button x:Name="AddAccount" Content="Add Account" Canvas.Left="145" Canvas.Top="333" HorizontalAlignment="Left" VerticalAlignment="Top" Width="140" RenderTransformOrigin="0.466,0.977" IsCancel="True" Height="40" Foreground="White" Background="Blue" Click="OnAddAccount"/>
<TextBlock x:Name="AccountsTextBlock" Canvas.Left="176" TextWrapping="Wrap" Text="Accounts" Canvas.Top="10" Height="33" Width="68" FontSize="16" FontFamily="Tahoma"/>
</Canvas>
<ListBox x:Name="accounts" HorizontalAlignment="Left" Height="237" VerticalAlignment="Top" Width="444" SelectionChanged="ListBox_SelectionChanged" Background="White" BorderBrush="White" Margin="0,47,0,0" RenderTransformOrigin="0.5,0.5">
<ListBox.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform AngleX="0.384"/>
<RotateTransform/>
<TranslateTransform X="-0.794"/>
</TransformGroup>
</ListBox.RenderTransform>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Height" Value="100"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="_Border" BorderBrush="Gray" BorderThickness="0.5"
Padding="2"
SnapsToDevicePixels="true">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="_Border" Property="Background" Value="LightSkyBlue"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image HorizontalAlignment="Left" VerticalAlignment="Center" Grid.Row="0" Grid.Column="0" Width="100" Source="{Binding Path=imagePath}" />
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="0" Grid.Column="1" Text="{Binding Path=userInfo}"></TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
public MainWindow()
{
InitializeComponent();
using (RegistryKey key = Registry.CurrentUser.CreateSubKey(authenticatorRegistryKey))
{
Object valueStr = Registry.GetValue(authenticatorRegistryKey, addedUsersRegistryValueName, null);
if (valueStr == null)
{
//no users added yet, hide list box and move add button
accounts.Visibility = Visibility.Collapsed;
AccountsTextBlock.Visibility = Visibility.Collapsed;
Canvas.SetLeft(AddAccountTextBlock, (Application.Current.MainWindow.Width - AddAccountTextBlock.Width) / 2);
Canvas.SetTop(AddAccountTextBlock, (Application.Current.MainWindow.Height - AddAccountTextBlock.Height) / 2 - AddAccount.Height + 10);
Canvas.SetLeft(AddAccount, (Application.Current.MainWindow.Width - AddAccount.Width) / 2);
Canvas.SetTop(AddAccount, (Application.Current.MainWindow.Height - AddAccount.Height) / 2);
}
else
{
//show the added user
List<UserInfo> items = new List<UserInfo>();
items.Add(new UserInfo()
{
userInfo = "abcdefg",
imagePath = ""
});
accounts.ItemsSource = items;
AccountsTextBlock.Visibility = Visibility.Visible;
accounts.Visibility = Visibility.Visible;
}
}
}
private void OnAddAccount(object sender, RoutedEventArgs e)
{
SignInUrlWindow signInUrlWindow = new SignInUrlWindow();
signInUrlWindow.Left = Application.Current.MainWindow.Left;
signInUrlWindow.Top = Application.Current.MainWindow.Top;
signInUrlWindow.Width = Application.Current.MainWindow.Width;
signInUrlWindow.Height = Application.Current.MainWindow.Height;
signInUrlWindow.ShowDialog();
List<UserInfo> items = new List<UserInfo>();
items.Add(new UserInfo()
{
userInfo = "abcdefg",
imagePath = ""
});
accounts.ItemsSource = items;
accounts.Visibility = Visibility.Visible;
AddAccount.Visibility = Visibility.Visible;
AccountsTextBlock.Visibility = Visibility.Visible;
}

View doesn't expand to fit a scaled Border

I have a Border inside a Grid which is displayed in a ChildWindow. I need to make the Border larger so I applied a RenderTransform to it. However the ChildWindow hasn't expanded to fit the scaled Border. It looks like the size is being calculated before the render transform is applied. This means that the border is now cropped and I can only see a 1/4 of it.
I've tried wrapping the Border in a ScrollViewer and a ViewBox, neither of which have worked.
<Grid>
...
<Border x:Name="Border"
Grid.Row="3"
Grid.ColumnSpan="2"
Background="White"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="5"
VerticalAlignment="Top"
Height="{Binding NewLabelTemplate.Height}"
Width="{Binding NewLabelTemplate.Width}">
<Border.RenderTransform>
<ScaleTransform CenterX="0.5"
CenterY="0.5"
ScaleX="2"
ScaleY="2"/>
</Border.RenderTransform>
...
</Border>
...
</Grid>
How can I get the ChildWindow to use the ultimate size of the border when calculating its size?
I can't apply either the scale to the ChildWindow or use the rendered height and width of the Border directly as there are other elements in the Grid that I don't want to be scaled.
At the moment I can think of two possible solutions:
1. Calculate Scaled Properties for Your Border to Bind to
<Grid>
<Grid.Resources>
<ScaledSizeProperties x:Key="BorderSizes"
ScaleFactor="2"
BorderThickness="1"
CornerRadius="5"
Height="{Binding NewLabelTemplate.Height}"
Width="{Binding NewLabelTemplate.Width}"/>
</Grid.Resources>
...
<Border x:Name="Border"
Grid.Row="3"
Grid.ColumnSpan="2"
Background="White"
BorderBrush="Black"
VerticalAlignment="Top"
BorderThickness="{Binding
Path=ScaledBorderThickness, Source={StaticResource BorderSizes}}"
CornerRadius="{Binding
Path=ScaledCornerRadius, Source={StaticResource BorderSizes}}"
Height="{Binding
Path=ScaledHeight, Source={StaticResource BorderSizes}}"
Width="{Binding
Path=ScaledWidth, Source={StaticResource BorderSizes}}">
...
</Border>
...
</Grid>
and code:
public class ScaledSizeProperties : DependencyObject
{
//add input DependencyProperties for:
//double ScaleFactor
//double BorderThickness
//double CornerRadius
//double Height
//double Width
//add output DependencyProperties for:
//double ScaledBorderThickness
//double ScaledCornerRadius
//double ScaledHeight
//double ScaledWidth
public double ScaleFactor
{
get { return (double) GetValue( ScaleFactorProperty ); }
set { SetValue( ScaleFactorProperty, value ); }
}
public static readonly DependencyProperty ScaleFactorProperty =
DependencyProperty.Register( "ScaleFactor", typeof( double ),
typeof( ScaledSizeProperties ),
new PropertyMetadata( 1, OnScaleFactorChanged ) );
private static void OnScaleFactorChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
//recalculate all size properties
}
public double Height
{
get ...
set ...
}
... DependencyProperty HeightProperty ... OnHeightChanged ...
private static void OnHeightChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
//recalculate ScaledHeight
}
}
2. Use a LayoutTransformer instead of RenderTransform
The LayoutTransformer that is part of the Toolkit is now (since SL5) official part of the base library package of Silverlight.
<Grid>
...
<LayoutTransformer
Grid.Row="3"
Grid.ColumnSpan="2"
VerticalAlignment="Top">
<LayoutTransformer.LayoutTransform>
<ScaleTransform CenterX="0.5"
CenterY="0.5"
ScaleX="2"
ScaleY="2"/>
</LayoutTransformer.LayoutTransform>
<Border x:Name="Border"
Background="White"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="5"
Height="{Binding NewLabelTemplate.Height}"
Width="{Binding NewLabelTemplate.Width}">
...
</Border>
</LayoutTransformer>
...
</Grid>

WPF - Custom design volume control

I have been working with WPF for some time.
I need to create the following control over Internet, but could not find appropriate.
Can anybody help how to implement this functionality. Value should be increasing or decreasing when clicked on control.
I found that I can use either Volume control or Slider, but not getting clear what I should use.
Thanks in anticipation.
I prefer to use a Progressbar for these kind of displays.
This is my implementation of a simple volume control looking pretty much like the one you show as an example:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private double _volume;
private bool mouseCaptured = false;
public double Volume
{
get { return _volume; }
set
{
_volume = value;
OnPropertyChanged("Volume");
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void MouseMove(object sender, MouseEventArgs e)
{
if (Mouse.LeftButton == MouseButtonState.Pressed && mouseCaptured)
{
var x = e.GetPosition(volumeBar).X;
var ratio = x/volumeBar.ActualWidth;
Volume = ratio*volumeBar.Maximum;
}
}
private void MouseDown(object sender, MouseButtonEventArgs e)
{
mouseCaptured = true;
var x = e.GetPosition(volumeBar).X;
var ratio = x / volumeBar.ActualWidth;
Volume = ratio * volumeBar.Maximum;
}
private void MouseUp(object sender, MouseButtonEventArgs e)
{
mouseCaptured = false;
}
#region Property Changed
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And the XAML:
<Window x:Class="VolumeControlApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="196" Width="319">
<Window.Resources>
<Style x:Key="VolumeStyle" TargetType="{x:Type ProgressBar}">
<Setter Property="Foreground" Value="#FFB00606"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ProgressBar}">
<Grid x:Name="TemplateRoot">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"/>
<Rectangle x:Name="PART_Track"/>
<Grid x:Name="PART_Indicator" ClipToBounds="True" HorizontalAlignment="Left">
<Rectangle x:Name="Indicator" Fill="{TemplateBinding Foreground}" RadiusX="5" RadiusY="3"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid Background="#FF363636">
<Border Background="Gray" BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center" CornerRadius="3" Padding="2">
<Border Background="Black" CornerRadius="3">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Vol:" Foreground="White" VerticalAlignment="Center" Margin="4 0"/>
<ProgressBar x:Name="volumeBar" Height="10"
Value="{Binding Volume}"
Width="100"
MouseMove="MouseMove"
MouseDown="MouseDown"
MouseUp="MouseUp" Style="{DynamicResource VolumeStyle}" Grid.Column="1"/>
</Grid>
</Border>
</Border>
</Grid>
</Window>
You could use a slider and create a template for it.
If you need special mouse handling you'll need to subclass the slider and add logic/event handling.
The standard Slider template has a couple of repeat buttons. By simply making the left repeat button red you have a very basic implementation of the required control.
Take a look at this posts hope it helps you..
Link:
1: Sliders
2: Similar to VLC player volume control

TemplateBinding in a nested template in Silverlight 4

I've implemented a control, CommandTextBox, which I want to be a text box with a button right next to it (so it almost appears within the text box).
The button should be an image which I can bind to an icon. It's fairly straightfoward stuff...
public class CommandTextBox : TextBox
{
/// <summary>
/// The image property.
/// </summary>
public static readonly DependencyProperty ImageProperty = DependencyProperty.Register(
"Image", typeof(ImageSource), typeof(CommandTextBox), null);
/// <summary>
/// Initializes a new instance of the <see cref = "CommandTextBox" /> class.
/// </summary>
public CommandTextBox()
{
this.DefaultStyleKey = typeof(CommandTextBox);
}
/// <summary>
/// Gets or sets the image.
/// </summary>
/// <value>
/// The image.
/// </value>
public ImageSource Image
{
get
{
return (ImageSource)this.GetValue(ImageProperty);
}
set
{
this.SetValue(ImageProperty, value);
}
}
}
I have a template as follows...
<Style TargetType="Controls:CommandTextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Controls:CommandTextBox">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{TemplateBinding Text}"/>
<Button Grid.Column="1"
Content="Search" >
<Button.Template>
<ControlTemplate>
<Image Source="{TemplateBinding Image}" />
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
But I get an error due to the template binding in the image. I sortof understand why, it's because the Template has changed now so the binding context isn't the same but I don't know how to overcome it.
Do I need to create a seperate ImageButton control so I can just do a normal template binding or is there another way?
Thanks
Ben
I have managed to get this to work by changing the style as follows:
<Style TargetType="appControls:CommandTextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="appControls:CommandTextBox">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{TemplateBinding Text}"/>
<Button Grid.Column="1" >
<Button.Content>
<Image DataContext="{TemplateBinding Image}" Source="{Binding}" />
</Button.Content>
</Button>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I'm not using a seperate template for the Button. My XAML with the control is:
<controls:CommandTextBox Text="Text" Image="/MyApp.Silverlight;component/Assets/Images/Amber_Triangle.png"></controls:CommandTextBox>
This appears to achieve the result you were after. The control renders on my test page as expected.

Slow wpf ItemsControl refresh on ListCollectionView

The problem I have is my listcollectionview is taking 3-4 seconds to update. I think I have the virtualization configured correctly; however, please point out if it is incorrect. I have my itemssource bound to a ICollectionView.
If i set the loop to 3000+ in BindingViewModel UI takes a long time to launch even though it takes less than a second to build the ViewModels.
I don't know how to attach files so I'll post all the sample code that reproduces this problem:
BigList.Xaml:
`
<Style x:Key="textBlockBaseStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="Blue"/>
</Style>
<Style x:Key="headerButtonStyle" TargetType="Button">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Blue"/>
<Setter Property="Background" Value="Transparent" />
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="DarkBlue"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="borderBaseStyle" TargetType="Border">
<Setter Property="BorderBrush" Value="Blue"/>
</Style>
<!--these two styles let us cascadingly style on the page without having to specify styles
apparently styles don't cascade into itemscontrols... in the items control we must reference via key -->
<Style TargetType="TextBlock" BasedOn="{StaticResource textBlockBaseStyle}"/>
<Style TargetType="Border" BasedOn="{StaticResource borderBaseStyle}"/>
<!-- hover styles -->
<Style x:Key="onmouseover" TargetType="{x:Type Border}" BasedOn="{StaticResource borderBaseStyle}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="DarkBlue"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<StackPanel Width="390">
<!-- Header-->
<Border BorderThickness="1,1,1,1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="55"/>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="70"/>
</Grid.ColumnDefinitions>
<Button Command="{Binding SortFirstNameCommand}" Style="{StaticResource headerButtonStyle}" Grid.Column="1" >
<TextBlock TextAlignment="Left" Text="First"/>
</Button>
<Button Style="{StaticResource headerButtonStyle}" Grid.Column="2" >
<TextBlock TextAlignment="Left" Text="Last"/>
</Button>
<Button Style="{StaticResource headerButtonStyle}" Grid.Column="3" >
<TextBlock TextAlignment="Left" Text="Activity"/>
</Button>
<Button Style="{StaticResource headerButtonStyle}" Grid.Column="4">
<TextBlock TextAlignment="Left" Text="Last Activity"/>
</Button>
</Grid>
</Border>
<Border BorderThickness="1,0,1,1">
<ItemsControl VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" ItemsSource="{Binding UsersAvailForEmail}" MaxHeight="400">
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer x:Name="ScrollViewer" HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto" IsDeferredScrollingEnabled="True" Padding="{TemplateBinding Padding}">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Style="{StaticResource onmouseover}" BorderThickness="0,0,1,1">
<CheckBox Margin="20,5" IsChecked="{Binding IsSelected}">
<CheckBox.Content>
<StackPanel Orientation="Horizontal">
<TextBlock Width="20"/>
<TextBlock Width="70" Style="{StaticResource textBlockBaseStyle}" Grid.Column="1" Text="{Binding FirstName}"/>
<TextBlock Width="70" Style="{StaticResource textBlockBaseStyle}" Grid.Column="2" Text="{Binding LastName}"/>
<TextBlock Width="70" Style="{StaticResource textBlockBaseStyle}" Grid.Column="3" Text="{Binding Action}"/>
<TextBlock Width="60" Style="{StaticResource textBlockBaseStyle}" Grid.Column="4" Text="{Binding ActionId}"/>
</StackPanel>
</CheckBox.Content>
</CheckBox>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</StackPanel>
</Grid>
`
BindingViewModel.cs:
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Windows.Data;
namespace LargeItemsCollection
{
public class BindingViewModel : INotifyPropertyChanged
{
ObservableCollection<EmailPersonViewModel> _usersAvail = new ObservableCollection<EmailPersonViewModel>();
ListCollectionView _lvc = null;
public BindingViewModel()
{
for (int i = 0; i < 4000; i++)
{
_usersAvail.Add( new EmailPersonViewModel { FirstName = "f" +i.ToString() , LastName= "l" + i.ToString(), Action= "a" + i.ToString(), ActionId="ai" + i.ToString(), IsSelected=true });
}
_lvc = new ListCollectionView(_usersAvail);
}
public ICollectionView UsersAvailForEmail
{
get { return _lvc; }
}
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
public virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
EmailPersonViewModel.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LargeItemsCollection
{
public class EmailPersonViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Action { get; set; }
public string ActionId { get; set; }
public bool IsSelected { get; set; }
public EmailPersonViewModel()
{
}
}
}
MainWindow.xaml:
<Window x:Class="LargeItemsCollection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LargeItemsCollection"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:BigList/>
</Grid>
</Window>
MainWindow.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace LargeItemsCollection
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new BindingViewModel();
}
}
}
All righty per this post:
Virtualizing a WPF ItemsControl
I didn't have the Scrollviewer.CanScroll='true' in WPF virtualizationstackpanel doesn't seem to work without it.
Thanks all who posted solutions to help me along the way. You guys didn't have the solution so I didn't mark it as correct; however, I gave you guys upvotes because you both helped along the way. Here's the final XAML so you can see what the itemscontrol needs to look like:
<ItemsControl ScrollViewer.CanContentScroll="True" VirtualizingStackPanel.VirtualizationMode="Recycling" ItemsSource="{Binding UsersAvailForEmail}" MaxHeight="400">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer x:Name="ScrollViewer" HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto" IsDeferredScrollingEnabled="True" Padding="{TemplateBinding Padding}">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Style="{StaticResource onmouseover}" BorderThickness="0,0,1,1">
<CheckBox Margin="20,5" IsChecked="{Binding IsSelected}">
<CheckBox.Content>
<StackPanel Orientation="Horizontal">
<TextBlock Width="20"/>
<TextBlock Width="70" Style="{StaticResource textBlockBaseStyle}" Grid.Column="1" Text="{Binding FirstName}"/>
<TextBlock Width="70" Style="{StaticResource textBlockBaseStyle}" Grid.Column="2" Text="{Binding LastName}"/>
<TextBlock Width="70" Style="{StaticResource textBlockBaseStyle}" Grid.Column="3" Text="{Binding Action}"/>
<TextBlock Width="60" Style="{StaticResource textBlockBaseStyle}" Grid.Column="4" Text="{Binding ActionId}"/>
</StackPanel>
</CheckBox.Content>
</CheckBox>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The virtualizing stack panel virtualizes creation of objects in the visual tree, not the objects in the ItemsSource. If it takes 4 seconds to create the collection of view model objects that the control is bound to, it's going to take 4 seconds to display the control irrespective of whether or not it uses virtualization.
Since you're sorting the collection, you have to instantiate all of the items in the collection before the control can display any of them. However long it takes to construct all 4,000 objects is a fixed cost here.
An easy way to test this is to create a command that builds the collection of objects and a second command that displays the items control bound to the collection. Then you can watch in the UI and see how long it takes to do each of those things separately.
If this is in fact the problem, you can focus on speeding up object creation (e.g. by using lazy evaluation on properties that are time-consuming to access, assuming they're not used by the sort), or possibly populate the collection in the background.
I guess it's due to the sorting approach that you've taken. Under the hood it uses reflection and this is quite a bottleneck. Consider using CustomSort property of the ListCollectionView class. More links in this answer.
Hope this helps.

Resources