I made a custom TreeView with multiple columns. Everything worked well until thee are lots of items in the tree.
I tried to enable Virtualization by doing VirtualizingPanel.IsVirtualizing="True" (Will be VirtualizingStackPanel.IsVirtualizing if you are < .NET 4.5) but not only it does not speed it up, it actually made the load time even worse.
On a normal TreeView this property does the trick but I can't find a way to get it to work on my custom Tree
TreeViewItem.cs
public class TreeListViewItem : TreeViewItem
{
protected override DependencyObject GetContainerForItemOverride()
{
return new TreeListViewItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TreeListViewItem;
}
}
TreeListView.cs
public class TreeListView : TreeView
{
public GridViewColumnCollection Columns { get; set; }
public TreeListView()
{
Columns = new GridViewColumnCollection();
}
protected override DependencyObject GetContainerForItemOverride()
{
return new TreeListViewItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TreeListViewItem;
}
}
Node.cs
public class Node
{
public string Data { get; set; }
public List<Node> Children { get; set; }
public Node(string data)
{
Data = data;
Children = new List<Node>();
}
}
ViewModel.cs
public class ViewModel
{
public ObservableCollection<Node> Nodes { get; private set; }
public ViewModel()
{
Nodes = new ObservableCollection<Node>();
Node parent = new Node("Parent");
for (int i = 0; i < 5000; i++)
parent.Children.Add(new Node(i.ToString()));
Nodes.Add(parent);
}
}
MainWindow.xaml
<Window x:Class="WPFTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFTest"
Title="Test"
mc:Ignorable="d"
Width="200">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="local:TreeListView">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TreeListView">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer VerticalScrollBarVisibility="Disabled">
<DockPanel>
<GridViewHeaderRowPresenter Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}"
DockPanel.Dock="Top"/>
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
DockPanel.Dock="Bottom">
<ItemsPresenter/>
</ScrollViewer>
</DockPanel>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="local:TreeListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TreeListViewItem">
<StackPanel>
<Border Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<GridViewRowPresenter x:Name="PART_Header"
Content="{TemplateBinding Header}"
Columns="{Binding Path=Columns, RelativeSource={RelativeSource AncestorType={x:Type local:TreeListView}}}" >
</GridViewRowPresenter>
</Border>
<ItemsPresenter x:Name="ItemsHost" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<local:TreeListView ItemsSource="{Binding Nodes}" VirtualizingPanel.IsVirtualizing="True">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}"/>
</TreeView.ItemTemplate>
<local:TreeListView.Columns>
<GridViewColumn Header="Test" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<DockPanel>
<TextBlock Text="{Binding Data}"/>
</DockPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</local:TreeListView.Columns>
</local:TreeListView>
</Grid>
</Window>
I tried templating the ItemPanel to be VirtualizingStackPanel and it did not help either.
I strip out the expander part since it is not relevant. You can double click on the parent node to expand the tree and it will take a long time to load the children.
On the TreeListView style, on the ItemsPresenter's parent ScrollViewer, set CanContentScroll="True":
<ScrollViewer CanContentScroll="True"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
DockPanel.Dock="Bottom">
<ItemsPresenter/>
</ScrollViewer>
On the TreeListViewItem style, you need to have something named "Expander" (for some reason unknown to me - perhaps some style/code is looking for it?).
Just put a ToggleButton in the style's StackPanel:
<StackPanel>
<ToggleButton x:Name="Expander" Width="0" />
<Border Name="Bd" ... />
....
</StackPanel>
Here is the complete XAML:
<Window x:Class="WpfApplication88.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication88"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="local:TreeListView">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TreeListView">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer VerticalScrollBarVisibility="Disabled">
<DockPanel>
<GridViewHeaderRowPresenter Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}"
DockPanel.Dock="Top"/>
<ScrollViewer CanContentScroll="True"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
DockPanel.Dock="Bottom">
<ItemsPresenter/>
</ScrollViewer>
</DockPanel>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="local:TreeListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TreeListViewItem">
<StackPanel>
<ToggleButton x:Name="Expander" Width="0" />
<Border Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<GridViewRowPresenter x:Name="PART_Header"
Content="{TemplateBinding Header}"
Columns="{Binding Path=Columns, RelativeSource={RelativeSource AncestorType={x:Type local:TreeListView}}}" >
</GridViewRowPresenter>
</Border>
<ItemsPresenter x:Name="ItemsHost" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<local:TreeListView ItemsSource="{Binding Nodes}" VirtualizingPanel.IsVirtualizing="True">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}"/>
</TreeView.ItemTemplate>
<local:TreeListView.Columns>
<GridViewColumn Header="Test" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<DockPanel>
<TextBlock Text="{Binding Data}"/>
</DockPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Test 2" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<DockPanel>
<TextBlock Text="{Binding Data2}"/>
</DockPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</local:TreeListView.Columns>
</local:TreeListView>
</Grid>
</Window>
I added a Test2 DataViewColum and added a Data2 property to the Node class to make sure it worked (and it does). Here are the changes to the code:
public class ViewModel
{
public ObservableCollection<Node> Nodes { get; private set; }
public ViewModel()
{
Nodes = new ObservableCollection<Node>();
Node parent = new Node("Parent", "Parent2");
for (int i = 0; i < 5000; i++)
parent.Children.Add(new Node(i.ToString(), (i * i).ToString()));
Nodes.Add(parent);
}
}
public class Node
{
public string Data { get; set; }
public string Data2 { get; set; }
public List<Node> Children { get; set; }
public Node(string data, string data2)
{
Data = data;
Data2 = data2;
Children = new List<Node>();
}
}
FYI - The template that VS made for me (while I was figuring this out), put the CanContentScroll="True" into a setter on a trigger for VirtualizingPanel.IsVirtualizing when it is True. Something like this:
<Style TargetType="local:TreeListView">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TreeListView">
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<GridViewHeaderRowPresenter Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}"
DockPanel.Dock="Top"/>
<ScrollViewer x:Name="_tv_scrollviewer_" HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
DockPanel.Dock="Bottom">
<ItemsPresenter/>
</ScrollViewer>
</DockPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="VirtualizingPanel.IsVirtualizing" Value="True">
<Setter Property="CanContentScroll" TargetName="_tv_scrollviewer_" Value="True"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Related
I created the Login Entities (Login.cs) and also I created Logins property of type DbSet of Login to save logins with (SubBVDbContext.cs) as public DbSet<Login> Logins { get; set;}, then I seed entries into the Login table in the (Configuration.cs) in Migration Folder. After that, I created the (LoginPageViewModel.cs) which includes LoginWrapper that wraps up all entities, as well as the (NavigationView.xaml) that allows navigation from model to another. I have already created (MainViewModel.cs), and the MainWindow.xaml that contain the views of the models and the navigations between them.
What I want is to display the LoginPage first then show the other views.
Login.cs
public class Login
{
public int Id { get; set; }
[Required]
[StringLength(50)]
[EmailAddress]
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
LoginDetailView.xaml(UserControl)
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Login.Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<PasswordBox PasswordChar="{Binding Login.Password, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<Button Width="100" ToolTip="Login to Subcontractors" HorizontalAlignment="Center" Content="login"/>
NavigationViewModel.xaml (UserControl)
<UserControl.Resources>
<Style x:Key="NaviItemContainerStyle" TargetType="ContentPresenter">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Margin" Value="2"/>
</Style>
<DataTemplate x:Key="NaviItemTemplate">
<Button Content="{Binding DisplayMember}"
Command="{Binding OpenDetailViewCommand}">
<Button.Template>
<ControlTemplate TargetType="Button">
<Grid x:Name="grid">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Foreground" Value="DarkRed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="300"/>
<RowDefinition/>
</Grid.RowDefinitions>
<GroupBox Header="Subcontarctors">
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Subs}"
ItemContainerStyle="{StaticResource NaviItemContainerStyle}"
ItemTemplate="{StaticResource NaviItemTemplate}"/>
</ScrollViewer>
</GroupBox>
<GroupBox Header="POs" Grid.Row="1" >
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto" >
<ItemsControl ItemsSource="{Binding SubPOs}"
ItemContainerStyle="{StaticResource NaviItemContainerStyle}"
ItemTemplate="{StaticResource NaviItemTemplate}"/>
</ScrollViewer>
</GroupBox>
</Grid>
LoginPageViewModel.cs
public class LoginPageViewModel:ViewModelBase
{
private LoginWrapper _login;
private IMessageDialogService _messageDialogService;
public LoginPageViewModel(IMessageDialogService messageDialogService)
{
_messageDialogService = messageDialogService;
}
public LoginWrapper Login
{
get { return _login; }
private set
{
_login = value;
OnPropertyChanged();
}
}
}
LoginWrapper.cs
public class LoginWrapper : ModelWrapper<Login>
{
public LoginWrapper(Login model) : base(model)
{
}
public int Id { get { return Model.Id; } }
public string Email
{
get { return GetValue<string>(); }
set { SetValue(value); }
}
public string Password
{
get { return GetValue<string>(); }
set { SetValue(value); }
}
}
App.xaml.cs
private void Application_Startup(object sender, StartupEventArgs e)
{
var bootstrapper = new Bootstrapper();
var container = bootstrapper.Bootstrap();
var mainWindow = container.Resolve<MainWindow>();
mainWindow.Show();
}
Note: in the Bootstrapper there is a container that contains the whole models and the views For Example:
public class Bootstrapper
{
public IContainer Bootstrap()
{
var builder = new ContainerBuilder();
builder.RegisterType<EventAggregator>().As<IEventAggregator>().SingleInstance();
builder.RegisterType<SubBVDbContext>().AsSelf();
builder.RegisterType<MainWindow>().AsSelf();
builder.RegisterType<SubPODetailViewModel>().As<ISubPODetailViewModel>();
builder.RegisterType<LoginPageViewModel>.AsSelf();
MainWindow.xaml
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:SubDetailViewModel}">
<view:SubDetailView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:SubPODetailViewModel}">
<view:SubPODetailView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:LoginPageViewModel}">
<view:LoginDetailView/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Menu Grid.ColumnSpan="2" FontSize="13">
<MenuItem Header="Create">
<MenuItem Header="New Subcontractor" Command="{Binding CreateNewDetailCommand}"
CommandParameter="{x:Type viewModel:SubDetailViewModel}"/>
<MenuItem Header="New P.O." Command="{Binding CreateNewDetailCommand}"
CommandParameter="{x:Type viewModel:SubPODetailViewModel}"/>
</MenuItem>
</Menu>
<view:NavigationView Grid.Row="1" DataContext="{Binding NavigationViewModel}"/>
<ContentControl Grid.Row="1" Grid.Column="1" Content="{Binding DetailViewModel}"/>
</Grid>
The Model view 1,The Model view 2
The SolutionExplorer Folders
The Link for My WPF App: http://www.filefactory.com/file/4of3jrkspmog/SubBvApp.zip
based on ItemsControl only shows first Item of each object I need your help again.
The solution worked out very well but after vacation and doing other stuff I need to modify it.
Actually there are Objects (CanvasObject) which are drawn on startup/after reading xml data and others (Module) which are dropped into the canvas from a treeview. These Modules are showing up in the canvas visual children but I cant see them at the canvas.
This is my code so far - I tried different approaches but always got similar results:
C#:
public class CanvasLine
{
public Point P1 { get; set; }
public Point P2 { get; set; }
public Brush Stroke { get; set; }
public double StrokeThickness { get; set; }
public DoubleCollection StrokeDashArray { get; set; }
}
public class CanvasObject
{
public String Name { get; set; }
public ObservableCollection<CanvasLine> CanvasLines { get; set; }
}
public class Module
{
public Dictionary<String, String> Attributes { get; set; }
public Point AnchorPoint { get; set; }
public Double Height { get; set; }
public Double Width { get; set; }
}
public class ViewModel
{
...
public ObservableCollection<Object> CanvasObjects;
...
}
The Viewmodels ObservableCollection<Object> gets filled with CanvasObjects from a xml - these ones are shown correctly. After that I'm adding Modules to that Collection by dropping them to the canvas. These ones seems to be into the canvas children collection but are not visible.
Here is the corresponding / cleaned up XAML:
<ItemsControl Grid.Column="1" Grid.Row="2" Margin="0" ItemsSource="{Binding CanvasObjects, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.Resources>
<ScaleTransform x:Key="LineTransform"
ScaleX="{Binding ActualWidth,
RelativeSource={RelativeSource AncestorType=ItemsControl}}"
ScaleY="{Binding ActualWidth,
RelativeSource={RelativeSource AncestorType=ItemsControl}}"/>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Transparent" AllowDrop="True" Drop="Canvas_OnDrop"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplateSelector>
<xmlTemp:TemplateSelector>
<xmlTemp:TemplateSelector.ModuleTemplate>
<DataTemplate DataType="{x:Type xmlElements:Module}">
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource Self}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding AnchorPoint.X}"/>
<Setter Property="Canvas.Top" Value="{Binding AnchorPoint.Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Width="{Binding Width}" Height="{Binding Height}" StrokeThickness="2" Fill="Black" Stroke="Black">
<Rectangle.RenderTransform>
<StaticResource ResourceKey="LineTransform"/>
</Rectangle.RenderTransform>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</xmlTemp:TemplateSelector.ModuleTemplate>
<xmlTemp:TemplateSelector.LineObjectTemplate>
<DataTemplate DataType="{x:Type dt:CanvasObject}">
<ItemsControl ItemsSource="{Binding CanvasLines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Stroke="{Binding Stroke}"
StrokeDashArray="{Binding StrokeDashArray}">
<Path.Data>
<LineGeometry
Transform="{StaticResource LineTransform}"
StartPoint="{Binding P1}"
EndPoint="{Binding P2}"/>
</Path.Data>
<Path.Style>
<Style TargetType="Path">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="StrokeThickness">
<Setter.Value>
<MultiBinding Converter="{StaticResource MultiplicationConverter}">
<Binding Path="StrokeThickness" Mode="OneWay"/>
<Binding Source="3"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="StrokeThickness" Value="{Binding StrokeThickness, Mode=OneWay}"/>
</Trigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</xmlTemp:TemplateSelector.LineObjectTemplate>
</xmlTemp:TemplateSelector>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
Here are the missing event and template selector:
private void Canvas_OnDrop(Object sender, DragEventArgs e)
{
Module Module = e.Data.GetData("Module") as Module;
Canvas Canvas = sender as Canvas;
if (Module != null && Canvas != null)
{
Module.Width = 0.1;
Module.Height = 0.2;
Module.AnchorPoint = e.GetPosition(Canvas);
ViewModel.CanvasObjects.Add(Module);
}
}
public class TemplateSelector : DataTemplateSelector
{
public DataTemplate LineObjectTemplate { get; set; }
public DataTemplate ModuleTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is CanvasObject)
return LineObjectTemplate;
if (item is Module)
return ModuleTemplate;
return base.SelectTemplate(item, container);
}
}
If I'm changing the ModuleTemplates ItemsControl to a ListBox I can see at least that there is some selected element in the top left corner after dropping a Module somewhere in the canvas. I'm thinking that the Rectangles Container might have no size but I'm not sure how to handle that correctly. Also the dropping position has no effect.
Possibly it's just a stupid error, just like the transparent, not dropable/hitable background which costs me over an hour today and I'm "seeing" it tomorrow after a few hours of sleep but every help and hint is highly appreciated!
Greetings
Edit:
THX to elgonzo I found the error. The Modules ItemsControl was unnecessary! THX again, perfect time to finish work now ;)
Following Code works fine:
<ItemsControl Grid.Column="1" Grid.Row="2" Margin="0" ItemsSource="{Binding CanvasObjects, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.Resources>
<ScaleTransform x:Key="LineTransform"
ScaleX="{Binding ActualWidth,
RelativeSource={RelativeSource AncestorType=ItemsControl}}"
ScaleY="{Binding ActualWidth,
RelativeSource={RelativeSource AncestorType=ItemsControl}}"/>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Transparent" AllowDrop="True" Drop="Canvas_OnDrop"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplateSelector>
<xmlTemp:TemplateSelector>
<xmlTemp:TemplateSelector.ModuleTemplate>
<DataTemplate DataType="{x:Type xmlElements:Module}">
<DataTemplate.Resources>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding AnchorPoint.X}"/>
<Setter Property="Canvas.Top" Value="{Binding AnchorPoint.Y}"/>
</Style>
</DataTemplate.Resources>
<Rectangle Width="{Binding Width}" Height="{Binding Height}" StrokeThickness="2" Fill="Black" Stroke="Black">
<Rectangle.RenderTransform>
<StaticResource ResourceKey="LineTransform"/>
</Rectangle.RenderTransform>
</Rectangle>
</DataTemplate>
</xmlTemp:TemplateSelector.ModuleTemplate>
<xmlTemp:TemplateSelector.LineObjectTemplate>
<DataTemplate DataType="{x:Type dt:CanvasObject}">
<ItemsControl ItemsSource="{Binding CanvasLines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Stroke="{Binding Stroke}"
StrokeDashArray="{Binding StrokeDashArray}">
<Path.Data>
<LineGeometry
Transform="{StaticResource LineTransform}"
StartPoint="{Binding P1}"
EndPoint="{Binding P2}"/>
</Path.Data>
<Path.Style>
<Style TargetType="Path">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="StrokeThickness">
<Setter.Value>
<MultiBinding Converter="{StaticResource MultiplicationConverter}">
<Binding Path="StrokeThickness" Mode="OneWay"/>
<Binding Source="3"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="StrokeThickness" Value="{Binding StrokeThickness, Mode=OneWay}"/>
</Trigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</xmlTemp:TemplateSelector.LineObjectTemplate>
</xmlTemp:TemplateSelector>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
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 trying to make a custom control which is basically a TextBox that sits above a datagrid column header and displays the total number in the datagrid column. The control library consists of two custom-controls: DataGridColumnTotal contains the textbox, which also holds references to the DataGrid and ItemsSource. The other is called DataGridHeaderTotalControl, which is basically a style that targets the DataGridColumnHeader. This style places the DataGridColumnTotal control above the existing DatagridColumnHeader and binds the DataGrid and DataGridItemsSource fields from the DataGridColumnTotal control to the Datagrid and ItemsSource fields.
I'm currently just trying to get the TextBox to show if the IsTotalVisible dependency property is set, but it will not update when I try to set it in MainWindow.xaml. I don't have any code in place that displays the total value because I haven't gotten that far yet.
The problem occurs in MainWindow.xaml, when trying to set ctl:DataGridColumnTotal.IsTotalVisible="True" in the dataGridText column. The IsTotalVisible property is not getting set to true. But when I set the default value of the dependency property to true, the total textboxes will show.
DataGridColumnTotal.cs
////////////////////////////////////////////////////////////////////
//DataGridColumnTotal.cs
////////////////////////////////////////////////////////////////////
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Collections;
namespace RiskControlLibrary
{
public class DataGridColumnTotal : Control
{
static DataGridColumnTotal()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DataGridColumnTotal), new FrameworkPropertyMetadata(typeof(DataGridColumnTotal)));
}
public static bool GetIsTotalVisible(
DependencyObject target)
{
return (bool)target.GetValue(IsTotalVisibleProperty);
}
public static void SetIsTotalVisible(
DependencyObject target, bool value)
{
target.SetValue(IsTotalVisibleProperty, value);
}
public static DependencyProperty IsTotalVisibleProperty =
DependencyProperty.RegisterAttached("IsTotalVisible", typeof(bool), typeof(DataGridColumnTotal),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits));
}
[ValueConversion(typeof(bool), typeof(Visibility))]
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((bool)value)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new Exception("Not Implemented.");
}
}
}
DataGridHeaderTotalControl.cs
////////////////////////////////////////////////////////////////////
//DataGridHeaderTotalControl.cs
////////////////////////////////////////////////////////////////////
using System.Windows;
using System.Windows.Controls;
namespace RiskControlLibrary
{
public class DataGridHeaderTotalControl : Control
{
static DataGridHeaderTotalControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DataGridHeaderTotalControl), new FrameworkPropertyMetadata(typeof(DataGridHeaderTotalControl)));
}
}
}
Generic.xaml
////////////////////////////////////////////////////////////////////
//Generic.xaml
////////////////////////////////////////////////////////////////////
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RiskControlLibrary"
xmlns:theme="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
xmlns:s="clr-namespace:System;assembly=mscorlib">
<Style TargetType="{x:Type local:DataGridColumnTotal}">
<Style.Resources>
<local:BoolToVisibilityConverter x:Key="booleanToVisibilityConverter" />
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:DataGridColumnTotal}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBox
x:Name="PART_TextBoxTotal"
IsReadOnly="True"
VerticalAlignment="Top"
VerticalContentAlignment="Center"
Text="{Binding Total,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:DataGridColumnTotal}}}"
Visibility="{Binding IsTotalVisible, RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource booleanToVisibilityConverter}}"
>
</TextBox>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type DataGridColumnHeader}"
x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:DataGridHeaderTotalControl},
ResourceId=DataGridHeaderTotalControlStyle}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<local:DataGridColumnTotal Margin="1" Grid.Column="0" Grid.Row="0"/>
<theme:DataGridHeaderBorder Grid.Column="0" Grid.Row="1" SortDirection="{TemplateBinding SortDirection}"
IsHovered="{TemplateBinding IsMouseOver}"
IsPressed="{TemplateBinding IsPressed}"
IsClickable="{TemplateBinding CanUserSort}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding ="{TemplateBinding Padding}"
SeparatorVisibility="{TemplateBinding SeparatorVisibility}"
SeparatorBrush="{TemplateBinding SeparatorBrush}">
<TextBlock Grid.Column="0" Grid.Row="1" Text="{TemplateBinding Content}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
TextWrapping="Wrap"></TextBlock>
</theme:DataGridHeaderBorder>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
MainWindow.xaml
////////////////////////////////////////////////////////////////////
//MainWindow.xaml
////////////////////////////////////////////////////////////////////
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:WpfApplication1"
xmlns:ctl="clr-namespace:RiskControlLibrary;assembly=RiskControlLibrary"
Title="MainWindow" Height="341" Width="452"
Loaded="Window_Loaded">
<Grid>
<DataGrid
Name="m_dgOrder"
ItemsSource="{Binding ListPos}"
GridLinesVisibility="None"
AutoGenerateColumns="False"
IsReadOnly="True"
Background="White"
CanUserResizeRows="False"
HeadersVisibility="Column"
RowHeaderWidth="0"
ColumnHeaderStyle="{StaticResource {ComponentResourceKey
TypeInTargetAssembly={x:Type ctl:DataGridHeaderTotalControl},
ResourceId=DataGridHeaderTotalControlStyle}}">
<DataGrid.Columns>
<DataGridTextColumn Header="Symbol" Binding="{Binding Path=Symbol}"/>
<DataGridTextColumn Header="Pos" Binding="{Binding Path=Pos}" ctl:DataGridColumnTotal.IsTotalVisible="True"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
I was registering the IsTotalVisible property to the wrong class. Instead of DataGridColumnTotal it should've been DataGridTextColumn.
Abstract:
I have two UserControls named Zone and ZoneGroup. One of these controls (ZoneGroup) includes two instances of the other one (Zone). Both of them set DataContext of the root element to this, at the Loaded event-handler.
The problem is that DataContext of inner controls (Zones) is set before loading (the DataContextChanged event occurred before Loaded) which causes some malfunctions in UI. (inside Zone controls initial state is wrong.) If I prevent it, everything works fine (at least seems to be!) except I encounter with the following error report. (In the Output window)
public partial class Zone : UserControl
{
∙∙∙
private void Zone_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// Adding this if-clause solve UI problems but makes some binding errors!
if (this.IsLoaded)
brdRoot.DataContext = this;
}
}
System.Windows.Data Error: 40 : BindingExpression path error: 'ZoneBrush' property not found on 'object' ''ZoneGroup' (Name='')'. BindingExpression:Path=ZoneBrush; DataItem='ZoneGroup' (Name=''); target element is 'brdRoot' (Name=''); target property is 'BorderBrush' (type 'Brush')
Details:
There is a UserControl named Zone containing several data-bindings like so..
<UserControl x:Class="MyApp.Zone"
∙∙∙>
<Border x:Name="brdRoot" BorderBrush="{Binding ZoneBrush}" BorderThickness="1">
∙∙∙
</Border>
</UserControl>
So, I set brdRoot data-context as
public partial class Zone : UserControl
{
public Brush ZoneBrush
{
get { return (Brush)GetValue(ZoneBrushProperty); }
set { SetValue(ZoneBrushProperty, value); }
}
∙∙∙
public Zone()
{
InitializeComponent();
}
private void Zone_Loaded(object sender, RoutedEventArgs e)
{
brdRoot.DataContext = this;
}
∙∙∙
}
Also, there is another UserControl that has two ContentPresenters in order to contain and manage two Zone controls.
<UserControl x:Class="MyApp.ZoneGroup"
∙∙∙>
<Border x:Name="brdRoot" BorderBrush="Gray" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<ContentPresenter Content="{Binding MainZone}"
Margin="{Binding MainZonePadding}"/>
<ContentPresenter Content="{Binding MemberZone}"/>
</StackPanel>
</Border>
</UserControl>
And the code-behind is:
public partial class ZoneGroup : UserControl
{
public Thickness MainZonePadding
{
get { return (Thickness)GetValue(MainZonePaddingProperty); }
set { SetValue(MainZonePaddingProperty, value); }
}
public Zone MainZone
{
get { return (Zone)GetValue(MainZoneProperty); }
set { SetValue(MainZoneProperty, value); }
}
public Zone MemberZone
{
get { return (Zone)GetValue(MemberZoneProperty); }
set { SetValue(MemberZoneProperty, value); }
}
public ZoneGroup()
{
InitializeComponent();
}
private void ZoneGroup_Loaded(object sender, RoutedEventArgs e)
{
brdRoot.DataContext = this;
}
∙∙∙
}
Edit ► Sketch:
My app works fine as expected, but some BindingExpression errors are reported.
You're overcomplicating it all too much, with all those UserControls and all those DependencyProperties. Look at this sample which uses 0 lines of C# code (XAML-Only):
<Window x:Class="MiscSamples.ItemsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ItemsControl" Height="300" Width="300">
<Window.Resources>
Style for the ItemsControl:
<Style TargetType="ItemsControl" x:Key="ZoneItemsControlStyle">
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Padding" Value="5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ItemsControl">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Padding="5">
<DockPanel>
<TextBlock HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Text="{Binding Items.Count,RelativeSource={RelativeSource TemplatedParent}, StringFormat='{}{0} Item(s)'}"
Foreground="{TemplateBinding Foreground}"
DockPanel.Dock="Top"/>
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<ItemsPresenter/>
</Border>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
DataTemplate For Brushes:
<DataTemplate DataType="{x:Type Brush}">
<Border BorderBrush="{Binding}" Margin="1" BorderThickness="1" Padding="2,3,2,3">
<TextBlock Text="{Binding}" TextAlignment="Center" Foreground="Black"/>
</Border>
</DataTemplate>
</Window.Resources>
Now, its usage:
<Grid>
<ItemsControl VerticalAlignment="Center" HorizontalAlignment="Center"
Style="{StaticResource ZoneItemsControlStyle}">
<SolidColorBrush Color="Red"/>
<SolidColorBrush Color="Green"/>
<SolidColorBrush Color="Black"/>
<SolidColorBrush Color="Blue"/>
</ItemsControl>
</Grid>
</Window>
Result:
See how I'm making use of DataTemplates in order to show a custom piece of UI for a specific Data Type? (In this case, System.Windows.Media.Brush class)
I'm "using the Brushes as ViewModels". You could also create your own ViewModels of course, and then create a specific DataTemplate for each VM type.
Also, see how I'm using the TemplateBinding MarkupExtension to bind several properties inside the ControlTemplate to the corresponding value in the ItemsControl instance.
Finally, see how you can actually add ANY kind of items to the ItemsControl.
Also, I must mention that I used this Style-based approach in order to enable reusability. You could place another ItemsControl somewhere else in the application and set its Style="{StaticResource ZoneItemsControlStyle}" and you're done. But if you only plan to use this a single time, you can just place all the properties hardcoded in the ItemsControl.Template ControlTemplate.
This is not a direct answer!
As #HighCore said, I tried to use an ItemsControl instead of implementing two ContentPresenters in my user-control. Just for clarity I made a new simple app to be able to describe it simply. So please consider some new assumptions:
Here again, there are two UserControls; MyItem and MyItemsControl as follows.
<UserControl x:Class="MyApp.MyItem"
∙∙∙>
<Grid x:Name="grdRoot">
<Border BorderBrush="{Binding ItemBorderBrsuh}" BorderThickness="1">
<TextBlock x:Name="txtColorIndicator"
Text="Item"
TextAlignment="Center"
Margin="5"/>
</Border>
</Grid>
</UserControl>
C# code-behind:
public partial class MyItem : UserControl
{
#region ________________________________________ ItemBorderBrsuh
public Brush ItemBorderBrsuh
{
get { return (Brush)GetValue(ItemBorderBrsuhProperty); }
set { SetValue(ItemBorderBrsuhProperty, value); }
}
public static readonly DependencyProperty ItemBorderBrsuhProperty =
DependencyProperty.Register("ItemBorderBrsuh",
typeof(Brush),
typeof(MyItem),
new FrameworkPropertyMetadata(new SolidColorBrush(Colors.Black), FrameworkPropertyMetadataOptions.None, OnItemBorderBrsuhPropertyChanged));
private static void OnItemBorderBrsuhPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MyItem instance = sender as MyItem;
if (instance != null && e.NewValue is SolidColorBrush)
instance.txtColorIndicator.Text = (e.NewValue as SolidColorBrush).Color.ToString();
}
#endregion
public MyItem()
{
InitializeComponent();
grdRoot.DataContext = this;
}
}
And this is the MyItemsControl.
<UserControl x:Class="MyApp.MyItemsControl"
∙∙∙>
<StackPanel>
<TextBlock x:Name="txtHeader" Margin="0,0,0,5" TextAlignment="Center" Text="0 Item(s)"/>
<Border BorderBrush="Gray" BorderThickness="1" Padding="5">
<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:MyItem />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</StackPanel>
</UserControl>
C# Code-behind:
public partial class MyItemsControl : UserControl
{
private ObservableCollection<MyItem> _Items = new ObservableCollection<MyItem>();
public ObservableCollection<MyItem> Items
{
get
{
return _Items;
}
set
{
_Items = value;
}
}
private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
txtHeader.Text = Items.Count + " Item(s)";
}
public MyItemsControl()
{
InitializeComponent();
Items.CollectionChanged += Items_CollectionChanged;
this.DataContext = this;
}
}
Here is how to use MyItem within MyItemsControl.
<Grid>
<local:MyItemsControl HorizontalAlignment="Center" VerticalAlignment="Center" Padding="5" BorderBrush="Black" BorderThickness="1">
<local:MyItemsControl.Items>
<local:MyItem ItemBorderBrsuh="Green" Margin="1"/>
<local:MyItem ItemBorderBrsuh="Red" Margin="1"/>
<local:MyItem ItemBorderBrsuh="Blue" Margin="1"/>
<local:MyItem ItemBorderBrsuh="Orange" Margin="1"/>
</local:MyItemsControl.Items>
</local:MyItemsControl>
</Grid>
Now, there is no problem with BindingExpressions, but an important question remains. How to replace
{
grdRoot.DataContext = this;
}
and
{
this.DataContext = this;
}
with a true ViewModel?
Screenshot:
Edit: I tried to implement MVVM pattern but there are some problems. I asked the first one here.