I'm sure this is a simple thing to do, and to look up, but I've yet to have any luck. Basically I want to style a WPF Slider such that when used I can give it two additional strings to display.
The new control looks like this:
public class SliderPicker : Slider
{
public string LeftLabel
{
get { return (string)GetValue(LeftLabelProperty); }
set { SetValue(LeftLabelProperty, value); }
}
public static readonly DependencyProperty LeftLabelProperty =
DependencyProperty.Register("LeftLabel", typeof(string), typeof(SliderPicker), new UIPropertyMetadata(String.Empty));
public string RightLabel
{
get { return (string)GetValue(RightLabelProperty); }
set { SetValue(RightLabelProperty, value); }
}
public static readonly DependencyProperty RightLabelProperty =
DependencyProperty.Register("RightLabel", typeof(string), typeof(SliderPicker), new UIPropertyMetadata(String.Empty));
}
The style like this:
<Style x:Key="SlickSlider" TargetType="{x:Type Slider}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Slider}">
...
<TextBlock Text="{TemplateBinding LeftLabel}" Grid.Row="2" HorizontalAlignment="Left" />
<TextBlock Text="{TemplateBinding RightLabel}" Grid.Row="2" HorizontalAlignment="Right" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And usage:
<controls:SliderPicker LeftLabel="RealPreference" RightLabel="RealCoverage" Width="400" Style="{StaticResource SlickSlider}"/>
This doesn't seem to work. So, how do I set the DP on the control and show it in the template? I thought that's what TemplateBinding was for?
You only need a small fix. Change {x:Type Slider} to {x:Type controls:SliderPicker}
You need to apply the custom control as the type in your style and template. Without it your trying to look for LeftLabelProperty and RightLabelProperty in Slider and not SliderPicker with your template binding.
Change your style to
<Style x:Key="SlickSlider" TargetType="{x:Type controls:SliderPicker}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:SliderPicker}">
...
<TextBlock Text="{TemplateBinding LeftLabel}" Grid.Row="2" HorizontalAlignment="Left" />
<TextBlock Text="{TemplateBinding RightLabel}" Grid.Row="2" HorizontalAlignment="Right" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Have tried this with the class you've posted and it works fine :)
Related
I'm trying to create a menu that works with radio buttons. The buttons are graphically prettied by a template. here I would like to display an icon and a text. However, I don't know how I can pass several parameters, so far I only pass the text and have not yet found a way to pass the image.
<StackPanel Grid.Row="1" Margin="0,10,0,0">
<RadioButton Content="Dashboard"
IsChecked="True"
Style="{StaticResource MenuButtonTheme}"/>
<RadioButton Content="Product"
Style="{StaticResource MenuButtonTheme}"/>
<RadioButton Content="Inventory"
Style="{StaticResource MenuButtonTheme}"/>
</StackPanel>
Style Of the Radiobutton
<Style BasedOn="{StaticResource {x:Type ToggleButton}}"
TargetType="{x:Type RadioButton}"
x:Key="MenuButtonTheme">
<Style.Setters>
<Setter Property="Foreground" Value="#FFFFFF"/>
<Setter Property="FontFamily" Value="/Fonts/#Poppins"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Border Background="{TemplateBinding Background}"
CornerRadius="5"
Margin="5,0,5,0">
<Grid VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Height="50"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<iconPacks:PackIconBoxIcons Kind="SolidPieChartAlt2"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="10,0,0,0"/>
<TextBlock Grid.Column="1" Text="{TemplateBinding Property=Content}"
VerticalAlignment="Center"
FontSize="20"
FontWeight="Regular"
Margin="10,0,0,0"/>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter Property="Background" Value="#212121"/>
<Setter Property="Foreground" Value="#4169E1"/>
</Trigger>
</Style.Triggers>
</Style>
Foreach particular part of UI in your application, I recommend you to make it a module, that is, a UserContol or ContentControl(recommened). These controls corresponds to View in MVVM, and foreach of them you should add a View Model.
namespace MyNameSpace{
public class View<T> : ContentControl {
public T ViewModel {
get { return (T)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(T), typeof(View<T>), new PropertyMetadata());
}
public abstract class ViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] String propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
If the relative logic is purely UI, then Model is not needed in this case.
Your View's xaml should look like this:
<local:View x:TypeArguments="MyAViewModel" x:Name="view"
x:Class="MyNameSpace.MyAView"
skip
xmlns:local="clr-namespace:MyNameSpace">
<Image Source="{Binding ViewModel.ImageSource,ElementName=view}"/>
</local:View>
Your ViewModel should look like this:
public class MyAViewModel: ViewModel {
public AbilityViewModel() {//Constructor with parameter
//Set the image source here
}
private ImageSource imageSource;
public ImageSource ImageSource{
get => imageSource
set{
imageSource = value;
NotifyPropertyChanged();
}
}
}
In the root element of your UI hierarchy, for example your MainWindow, add your custom contols:
<Window x:Name="window" skip>
<Grid>
<local:MyAView ViewModel="{Binding MyAViewModel,ElementName=window}"/>
<local:MyBView ViewModel="{Binding MyBViewModel,ElementName=window}"/>
</Grid>
</Window>
You may either do so with adding dependency properies of the MyAViewModel and MyBViewModel to your MainWindow, or just set MyAView's ViewModel in MainWindow's constructor or loaded event. You may create the ViewModel to pass to view, in which ImageSource is initialized in constructor, or change it after its construction by somewhere in your code.
Above codes are just demo, directly written in stackoverflow's webpage and is not tested. You may ask me if there is any problem.
I am trying to bind some values to ToolTipService.ShowDuration and some other ToolTip's properties in a CellStyle of DataGridTextColumn.
Normaly, I am doing something like this:
<UserControl
...namespace declarations...>
<UserControl.Resources>
<mycontrols:BindingProxy x:Key="proxy" Data="{Binding MySettings}"/>
</UserControl.Resources>
<DataGrid>
<DataGridTextColumn
Binding="{Binding SomeBinding}">
<DataGridTextColumn.CellStyle>
<Style
TargetType="DataGridCell"
BasedOn="{StaticResource ResourceKey={x:Type DataGridCell}}">
<Setter
Property="ToolTipService.ShowDuration"
Value="{Binding Data.ToolTipDuration, Source={StaticResource proxy}}"/>
<Setter Property="ToolTip">
<Setter.Value>
<TextBlock
Text="{Binding SomeBinding}"
MaxWidth="{Binding Data.ToolTipMaxWidth, Source={StaticResource proxy}}"
TextWrapping="Wrap" TextTrimming="CharacterEllipsis"/>
</Setter.Value>
</Setter>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
</DataGrid>
</UserControl>
Since ToolTip has it's own visual tree, I am using binding proxy like this:
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
Up to this point, all works as expected. But I wanted to re-use this DataGridTextColumn, so I created new file like this:
<DataGridTextColumn
x:Class="Test.MyControls.DataGridLargeTextColumn"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test.MyControls">
<DataGridTextColumn.CellStyle>
<Style
TargetType="DataGridCell"
BasedOn="{StaticResource ResourceKey={x:Type DataGridCell}}">
<Setter
Property="ToolTipService.ShowDuration"
Value="{Binding ToolTipDuration}"/>
<Setter Property="ToolTip">
<Setter.Value>
<TextBlock
Text="{Binding SomeBinding}"
MaxWidth="{Binding ToolTipWidth}"
TextWrapping="Wrap" TextTrimming="CharacterEllipsis"/>
</Setter.Value>
</Setter>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
With code behinde:
public partial class DataGridLargeTextColumn : DataGridTextColumn
{
public int ToolTipDuration
{
get { return (int)GetValue(ToolTipDurationProperty); }
set { SetValue(ToolTipDurationProperty, value); }
}
public static readonly DependencyProperty ToolTipDurationProperty =
DependencyProperty.Register("ToolTipDuration", typeof(int), typeof(DataGridLargeTextColumn), new UIPropertyMetadata(default(int)));
public string SomeBinding
{
get { return (string)GetValue(SomeBindingProperty); }
set { SetValue(SomeBindingProperty, value); }
}
public static readonly DependencyProperty SomeBindingProperty =
DependencyProperty.Register("SomeBinding", typeof(string), typeof(DataGridLargeTextColumn), new UIPropertyMetadata(default(string)));
public int ToolTipWidth
{
get { return (int)GetValue(ToolTipWidthProperty); }
set { SetValue(ToolTipWidthProperty, value); }
}
public static readonly DependencyProperty ToolTipWidthProperty =
DependencyProperty.Register("ToolTipWidth", typeof(int), typeof(DataGridLargeTextColumn), new UIPropertyMetadata(default(int)));
public DataGridLargeTextColumn()
{
InitializeComponent();
}
}
This does't work because ToolTip has it's own visual tree, but since I have nowhere to put proxy, I don't know how to make it work or if it is even possible. I found this answer, and it seems to be on the right track, however, I tried to implement it like this with no luck:
<Setter
Property="ToolTipService.ShowDuration"
Value="{Binding Path=PlacementTarget.(local:DataGridLargeTextColumn.ToolTipDuration), RelativeSource={RelativeSource Self}}"/>
<Setter Property="ToolTip">
<Setter.Value>
<TextBlock
Text="{Binding Path=PlacementTarget.(local:DataGridLargeTextColumn.SomeBinding), RelativeSource={RelativeSource Self}}"
MaxWidth="{Binding Path=PlacementTarget.(local:DataGridLargeTextColumn.ToolTipWidth), RelativeSource={RelativeSource Self}}"
TextWrapping="Wrap" TextTrimming="CharacterEllipsis"/>
</Setter.Value>
</Setter>
Am I using PlacementTarget wrong? If not, why is it not working, and is there another solution?
UPDATE:
As per Mark's answer, I've modified the DataGridLargeTextColumn's Style:
<Style
TargetType="DataGridCell"
BasedOn="{StaticResource {x:Type DataGridCell}}">
<Setter
Property="ToolTipService.ShowDuration" Value="{Binding Path=PlacementTarget.ToolTipShowDuration, RelativeSource={x:Static RelativeSource.Self}}"/>
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding Path=PlacementTarget, RelativeSource={x:Static RelativeSource.Self}}">
<TextBlock
Text="{Binding DataContext.SomeBinding}"
MaxWidth="{Binding Column.ToolTipWidth}"
TextWrapping="Wrap" TextTrimming="CharacterEllipsis"/>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
And I'm using that control like this:
<UserControl
...namespace declarations...>
<UserControl.Resources>
<mycontrols:BindingProxy x:Key="proxy" Data="{Binding MySettings}"/>
</UserControl.Resources>
<DataGrid>
<DataGrid.Columns>
<mycontrols:DataGridLargeTextColumn
Binding="{Binding SomeBinding}"
ToolTipShowDuration="{Binding Data.ToolTipDuration, Source={StaticResource proxy}}"
ToolTipWidth="{Binding Data.ToolTipMaxWidth, Source={StaticResource proxy}}"/>
</DataGrid.Columns>
</DataGrid>
</UserControl>
Width binding now works like a charm, but there are two problems I still cannot solve:
I cannot get tool tip's duration to bind, I have tried few different approches, but since it's abstract, it cannot be declared explicitly
ToolTip's Text property is set to SomeBinding, which is OK in this particular case, but I want to be able to set it to whatever, so I tried to declare it using DependencProperty from above like this:
Text="{Binding Column.ToolTipText}"
This works OK if i use it with string literal:
<myControls:DataGridLargeTextColumn
Binding="{Binding SomeBinding}"
ToolTipText="12345"
ToolTipShowDuration="{Binding Data.ToolTipDuration, Source={StaticResource proxy}}"
ToolTipWidth="{Binding Data.ToolTipMaxWidth, Source={StaticResource proxy}}"/>
But it doesn't work when I try to bind it, which is what I am trying to achieve:
<myControls:DataGridLargeTextColumn
Binding="{Binding SomeBinding}"
ToolTipText="{Binding SomeOtherPropertyBinding}"
ToolTipShowDuration="{Binding Data.ToolTipDuration, Source={StaticResource proxy}}"
ToolTipWidth="{Binding Data.ToolTipMaxWidth, Source={StaticResource proxy}}"/>
By default your ToolTip's DataContext gets set to the DataContext of the cell. You, however, are trying to bind to dependency properties in the cell's column instead, so you're going to have to change the DataContext to point to the cell itself and then reference DataContext explicitly when you want to access the data and Column when you want to access the DPs in your DataGridLargeTextColumn.
In other words, declare the ToolTip explicitly in addition to its content and set its DataContext, like this:
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding Path=PlacementTarget, RelativeSource={x:Static RelativeSource.Self}}">
<TextBlock
Text="{Binding DataContext.SomeBinding}"
Width="{Binding Column.ToolTipWidth}" />
</ToolTip>
</Setter.Value>
</Setter>
...where Text in this case is binding to the data and Width is binding to the custom column DP.
Alternatively you could also just leave the DataContext as is and instead use the ToolTip's Tag property as a binding proxy to the DataGridLargeTextColumn:
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip Tag="{Binding Path=PlacementTarget.Column, RelativeSource={x:Static RelativeSource.Self}}">
<TextBlock
Text="{Binding SomeBinding}"
Width="{Binding Tag.ToolTipWidth, RelativeSource={RelativeSource AncestorType=ToolTip}}" />
</ToolTip>
</Setter.Value>
</Setter>
All the default property bindings are working fine however my custom properties are using the fallback values. Specifically ColorThumb and ColorThumbBorder. As their names imply, these 2 properties color the respective parts of the slider's thumb using a brush.
I've had no problems in the past getting my styles to use custom properties for other controls. Is there something unique about the slider control?
Style Xaml
<Style TargetType="{x:Type local:DMSlider}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Slider}">
<Border>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" MinHeight="{TemplateBinding MinHeight}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TickBar x:Name="TopTick" Visibility="{TemplateBinding TickPlacement}" Fill="{TemplateBinding Foreground}" Placement="Top" Height="4" Grid.Row="0"/>
<TickBar x:Name="BottomTick" Visibility="{TemplateBinding TickPlacement}" Fill="{TemplateBinding Foreground}" Placement="Bottom" Height="4" Grid.Row="0"/>
<Border x:Name="TrackBackground" BorderThickness="1" CornerRadius="0" Margin="5,0" VerticalAlignment="Center" Height="4.0" Grid.Row="1"
Background="{Binding Background, RelativeSource={RelativeSource Mode=TemplatedParent}}"
BorderBrush="{Binding BorderBrush, RelativeSource={RelativeSource Mode=TemplatedParent}}">
<Canvas Margin="-6,-1">
<Rectangle Visibility="Hidden" x:Name="PART_SelectionRange" Height="4.0" StrokeThickness="1.0"/>
</Canvas>
</Border>
<Track x:Name="PART_Track" Grid.Row="1">
<Track.DecreaseRepeatButton>
<RepeatButton Command="{x:Static Slider.DecreaseLarge}" Style="{StaticResource DMSliderRepeatButton}"/>
</Track.DecreaseRepeatButton>
<Track.IncreaseRepeatButton>
<RepeatButton Command="{x:Static Slider.IncreaseLarge}" Style="{StaticResource DMSliderRepeatButton}"/>
</Track.IncreaseRepeatButton>
<Track.Thumb>
<Thumb x:Name="Thumb">
<Thumb.Style>
<Style TargetType="{x:Type Thumb}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border x:Name="gripBorder" BorderThickness="1"
Background="{Binding ColorThumb,
RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay, FallbackValue=Red}"
BorderBrush="{Binding ColorThumbBorder,
RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay, FallbackValue=Green}" >
<Rectangle x:Name="grip" Height="15" Width="10" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Thumb.Style>
</Thumb>
</Track.Thumb>
</Track>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Properties CS
public partial class DMSlider : Slider
{
public static readonly DependencyProperty ColorSliderLeftProperty =
DependencyProperty.Register("ColorSliderLeft", typeof(Brush), typeof(DMSlider));
public static readonly DependencyProperty ColorSliderRightProperty =
DependencyProperty.Register("ColorSliderRight", typeof(Brush), typeof(DMSlider));
public static readonly DependencyProperty ColorSliderThumbProperty =
DependencyProperty.Register("ColorThumb", typeof(Brush), typeof(DMSlider), new FrameworkPropertyMetadata(Brushes.Orange));
public static readonly DependencyProperty ColorSliderThumbHoverProperty =
DependencyProperty.Register("ColorThumbHover", typeof(Brush), typeof(DMSlider));
public static readonly DependencyProperty ColorSliderThumbBorderProperty =
DependencyProperty.Register("ColorThumbBorder", typeof(Brush), typeof(DMSlider));
public Brush ColorSliderLeft
{
get { return (Brush)GetValue(ColorSliderLeftProperty); }
set { SetValue(ColorSliderLeftProperty, value); }
}
public Brush ColorSliderRight
{
get { return (Brush)GetValue(ColorSliderRightProperty); }
set { SetValue(ColorSliderRightProperty, value); }
}
public Brush ColorThumb
{
get { return (Brush)GetValue(ColorSliderThumbProperty); }
set { SetValue(ColorSliderThumbProperty, value); }
}
public Brush ColorThumbHover
{
get { return (Brush)GetValue(ColorSliderThumbHoverProperty); }
set { SetValue(ColorSliderThumbHoverProperty, value); }
}
public Brush ColorThumbBorder
{
get { return (Brush)GetValue(ColorSliderThumbProperty); }
set { SetValue(ColorSliderThumbBorderProperty, value); }
}
}
Thanks to #Clemens for showing me what I was doing wrong. I needed to reference the slider not the the thumb.
I should have been targeting an ancestor like so:
Background="{Binding ColorThumb, RelativeSource={RelativeSource AncestorType=local:DMSlider}, Mode=TwoWay, FallbackValue=Red}"
BorderBrush="{Binding ColorThumbBorder, RelativeSource={RelativeSource AncestorType=local:DMSlider}, Mode=TwoWay, FallbackValue=Green}"
Full thumb style
<Thumb x:Name="Thumb">
<Thumb.Style>
<Style TargetType="{x:Type Thumb}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border x:Name="gripBorder" BorderThickness="1"
Background="{Binding ColorThumb,
RelativeSource={RelativeSource AncestorType=local:DMSlider}, Mode=TwoWay, FallbackValue=Red}"
BorderBrush="{Binding ColorThumbBorder,
RelativeSource={RelativeSource AncestorType=local:DMSlider}, Mode=TwoWay, FallbackValue=Green}">
<Rectangle x:Name="grip" Height="15" Width="10" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Thumb.Style>
I'm writing my first custom user control against WinRT, and I've run into a problem.
I would like to expose an image, PART_NwBadge and it's visibility as Dependency Properties in my control. Then I would like to supply default values through setters in the style. This part is not working. Instead, the default value from the DependencyProperty (in BadgedButton.cs) is being applied.
Is it even possible to do what I've described? Or should I be setting the default values in the C# code? If I do need to set the values in the C# code, would someone comment on how to load image resources in code? After a great deal of searching I've yet to find a solution that works.
Finally, since this is my first serious attempt at writing a custom control, please suggest any improvements I could make, even if they are not directly related to the problem.
Windows 8 Consumer Preview
C#/WinRT/Metro
Visual Studio 11 Beta
Themes/Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="using:InkSdkTestApplication.Controls">
<Style TargetType="l:BadgedButton">
<Setter Property="Width" Value="36"/>
<Setter Property="Height" Value="36"/>
<Setter Property="Background" Value="#1C1C1C"/>
<Setter Property="BorderBrush" Value="White"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="NwBadge">
<Setter.Value>
<Image Width="16" Height="16" Source="../Assets/mouse_16x16.png"/>
</Setter.Value>
</Setter>
<Setter Property="NwBadgeVisibility" Value="Collapsed"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="l:BadgedButton">
<Border x:Name="PART_Border"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<ContentPresenter x:Name="PART_Content"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"/>
<Image x:Name="PART_NwBadge"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="16" Height="16"
Visibility="{TemplateBinding NwBadgeVisibility}"
Source="{TemplateBinding NwBadge}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Controls/BadgedButton.cs
namespace InkSdkTestApplication.Controls
{
public sealed class BadgedButton : Control
{
#region // Dependency Properties
public static DependencyProperty ContentProperty =
DependencyProperty.Register(
"Content",
typeof(FrameworkElement),
typeof(BadgedButton),
new PropertyMetadata(null));
public static DependencyProperty NwBadgeProperty =
DependencyProperty.Register(
"NwBadge",
typeof(Image),
typeof(BadgedButton),
new PropertyMetadata(null));
public static DependencyProperty NwBadgeVisibilityProperty =
DependencyProperty.Register(
"NwBadgeVisibility",
typeof(Visibility),
typeof(BadgedButton),
new PropertyMetadata(Visibility.Visible));
#endregion
#region // Public Properties
public FrameworkElement Content
{
get { return (FrameworkElement)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public Image NwBadge
{
get { return (Image)GetValue(NwBadgeProperty); }
set { SetValue(NwBadgeProperty, value); }
}
public Visibility NwBadgeVisibility
{
get { return (Visibility)GetValue(NwBadgeVisibilityProperty); }
set { SetValue(NwBadgeVisibilityProperty, value); }
}
#endregion
public BadgedButton()
{
this.DefaultStyleKey = typeof(BadgedButton);
}
}
}
http://timheuer.com/blog/archive/2012/03/07/creating-custom-controls-for-metro-style-apps.aspx
I am trying to create a custom Tag Cloud control. The way it's supposed to work is that the user can give it a collection of strings in the itemsSource and the converted string will be displayed in the UI. The problem is when I try to drop the control into an application I get the following error:"Could not create an instance of type TagCloudControl". Can anyone help?
Code behind
public class TagCloudControl : ListBox
{
static TagCloudControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TagCloudControl), new FrameworkPropertyMetadata
(typeof(TagCloudControl)));
}
//tags dependency property
public static DependencyProperty TagsProperty = DependencyProperty.Register("Tags",
typeof(IEnumerable<Tag>),
typeof(TagCloudControl));
public CollectionView GroupedTagsView { get; set; }
public TagCloudControl()
{
ItemsSource = Tags;
//group my tags by "name" property
GroupedTagsView = (ListCollectionView)CollectionViewSource.GetDefaultView(Tags);
GroupedTagsView.GroupDescriptions.Add(new PropertyGroupDescription("Name"));
}
public IEnumerable<Tag> Tags
{
get { return (IEnumerable<Tag>)GetValue(TagsProperty); }
set { SetValue(TagsProperty, value); }
}
}
XAML
<Window x:Class="TagCloudDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:TagCloudControlLibrary;assembly=TagCloudControlLibrary"
Title="Window1" Height="300" Width="300">
<Grid>
<src:TagCloudControl HorizontalAlignment="Center" VerticalAlignment="Center" Name="firstTag"/>
</Grid>
</Window>
TagControl xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TagCloudControlLibrary">
<local:CountToBrushConverter x:Key="CountToBrushConverter"/>
<local:CountToFontSizeConverter x:Key="CountToFontSizeConverter"/>
<Style TargetType="{x:Type local:TagCloudControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TagCloudControl}">
<Grid>
<WrapPanel Orientation="Horizontal"
Margin="2"
IsItemsHost="True">
</WrapPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="TagsTemplate">
<WrapPanel>
<TextBlock Text="{Binding Name}"
FontSize="{Binding ItemCount, Converter={StaticResource CountToBrushConverter}}"
Foreground="{Binding ItemCount, Converter={StaticResource CountToFontSizeConverter}}"/>
</WrapPanel>
</DataTemplate>
</ResourceDictionary>
Tag Class
public class Tag
{
public Tag(string name)
{
Name = name;
}
public string Name { get; set;}
}
Do you need to instantiate Tags?
I'm guessing it's a null reference exception, you set your items source as Tags in your constructor, but you have not instantiated Tags.
public TagCloudControl()
{
Tags = new ObservableCollection<Tags>();
ItemsSource = Tags;
...
}
Edit:
After playing around with code... I'm thinking that Tags might not need to be a DependencyProperty. It appears you want to Bind the ItemsSource to Tags... but you could just make Tags a simple property, and in it, set the ItemsSource to the value passed in:
public TagCloudControl()
{
//this is now empty.
}
public IEnumerable<Tag> Tags
{
get { return (this.ItemsSource as IEnumerable<Tag>); }
set { this.ItemsSource = value; }
}
Also I think your Style might want to be more like this:
<Style TargetType="{x:Type local:TagCloudControl}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type local:Tag}">
<TextBlock Text="{Binding Name}"
FontSize="{Binding ItemCount, Converter={StaticResource CountToBrushConverter}}"
Foreground="{Binding ItemCount, Converter={StaticResource CountToFontSizeConverter}}">
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TagCloudControl}">
<Grid>
<WrapPanel Orientation="Horizontal" Margin="2" IsItemsHost="True">
</WrapPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And finally, I think you might be missing ItemCount in your 'Tag' Class.