WPF ItemControl slow rendering - c#

Hi there I have a WPF application with an image where the user selects an area, once the area is selected a grid with crosses appears over the selected area. I apply some transformations back and forth in order to scale and rotate the grid to match the image coordinates.This is working so far, but when there is a lot of crosses (~+5k) the UI freezes and takes ages to render the crosses. I have applied some answers I found over Stackoverflow, like virtualization, ListView, ListBox, but I cannot make it work. I am wondering if someone can put some light here, thanks in advance!.EDITSo I end up doing all the related calculation to translate the crosses on the ViewModel, in order to do this and not break the MVVM pattern, I use AttachedProperties which gives me on the ViewModel the data needed for the calculation of the positions. Here is the link and the explanation -> https://stackoverflow.com/a/3667609/2315752 Here is the main code:MainWindow.ItemControl:
<ItemsControl ItemsSource="{Binding Crosses}">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left"
Value="{Binding X}" />
<Setter Property="Canvas.Top"
Value="{Binding Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Width="5"
Height="5"
StrokeThickness="1"
Stroke="1"
Style="{StaticResource ShapeCross}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

The key is to layout the item containers on the Canvas and not the items. This way the rendering occurs during the panel's arrange layout pass. Translating the item elements (the content of the item containers) after the containers have been rendered adds additional render time.
Instead of translating the points across the Canvas you should use the attached properties Canvas.Left and Canvas.Top to layout the item containers on the Canvas panel.
The graph manipulation like scaling should be done in the view model directly on the set of data items. To allow dynamic UI updates consider to implement a custom data model which implements INotifyPropertyChanged e.g. ObservablePoint.
The following example draws a sine graph of crosses. The graph consists of 10,800 data points. Load up time is approximately less than 5 seconds, which are spent to create the 10,800 Point instances.
The result is instant rendering and pretty smooth scrolling:
ViewModel.cs
class ViewModel
{
public ObservableCollection<Point> Points { get; set; }
public ViewModel()
{
this.Points = new ObservableCollection<Point>();
// Generate a sine graph of 10,800 points
// with an amplitude of 200px and a vertical offset of 200px
for (int x = 0; x < 360 * 30; x++)
{
var point = new Point()
{
X = x,
Y = Math.Sin(x * Math.PI / 180) * 200 + 200};
}
this.Points.Add(point);
}
}
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<ListBox ItemsSource="{Binding Points}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="11000" Height="500" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="Point">
<Grid>
<Line Stroke="Black" StrokeThickness="2" X1="0" X2="10" Y1="5" Y2="5" />
<Line Stroke="Black" StrokeThickness="2" X1="5" X2="5" Y1="0" Y2="10" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Window>

You can make a class that calculates the scale and passes it to the ViewModel.
An approximate implementation and its use.You can make a class that calculates the scale and passes it to the ViewModel.
An approximate implementation and its use.
public class ScaleCalcBinding : Freezable
{
public FrameworkElement SourceElement
{
get { return (FrameworkElement)GetValue(SourceElementProperty); }
set { SetValue(SourceElementProperty, value); }
}
// Using a DependencyProperty as the backing store for SourceElement. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SourceElementProperty =
DependencyProperty.Register(nameof(SourceElement), typeof(FrameworkElement), typeof(ScaleCalcBinding), new PropertyMetadata(null, ElementChanged));
private static void ElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScaleCalcBinding dd = (ScaleCalcBinding)d;
FrameworkElement element = e.OldValue as FrameworkElement;
if (element != null)
element.SizeChanged -= dd.CalcScale;
element = e.NewValue as FrameworkElement;
if (element != null)
element.SizeChanged += dd.CalcScale;
dd.CalcScale();
}
private void CalcScale(object sender = null, SizeChangedEventArgs e = null)
{
if (SourceElement == null || TargetElement == null || ScanScale == null)
{
ScaleWidthResult = null;
ScaleHeightResult = null;
return;
}
ScaleWidthResult = SourceElement.ActualWidth / TargetElement.ActualWidth * ScanScale.Value;
ScaleHeightResult = SourceElement.ActualHeight / TargetElement.ActualHeight * ScanScale.Value;
}
public FrameworkElement TargetElement
{
get { return (FrameworkElement)GetValue(TargetElementProperty); }
set { SetValue(TargetElementProperty, value); }
}
// Using a DependencyProperty as the backing store for TargetElement. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TargetElementProperty =
DependencyProperty.Register(nameof(TargetElement), typeof(FrameworkElement), typeof(ScaleCalcBinding), new PropertyMetadata(null));
public double? ScanScale
{
get { return (double?)GetValue(ScanScaleProperty); }
set { SetValue(ScanScaleProperty, value); }
}
// Using a DependencyProperty as the backing store for ScanScale. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ScanScaleProperty =
DependencyProperty.Register(nameof(ScanScale), typeof(double?), typeof(ScaleCalcBinding), new PropertyMetadata(null, ElementChanged));
public double? ScaleWidthResult
{
get { return (double?)GetValue(ScaleResultWidthProperty); }
set { SetValue(ScaleResultWidthProperty, value); }
}
// Using a DependencyProperty as the backing store for ScaleWidthResult. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ScaleResultWidthProperty =
DependencyProperty.Register(nameof(ScaleWidthResult), typeof(double?), typeof(ScaleCalcBinding), new PropertyMetadata(null));
public double? ScaleHeightResult
{
get { return (double?)GetValue(ScaleHeightResultProperty); }
set { SetValue(ScaleHeightResultProperty, value); }
}
// Using a DependencyProperty as the backing store for ScaleHeightResult. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ScaleHeightResultProperty =
DependencyProperty.Register(nameof(ScaleHeightResult), typeof(double?), typeof(ScaleCalcBinding), new PropertyMetadata(null));
protected override Freezable CreateInstanceCore() => new ScaleCalcBinding();
}
XAML
<Window
x:Name="window"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CF2002"
x:Class="CF2002.MainWindow"
Title="MainWindow"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
Foreground="White"
Background="#FF79C2FF"
Height="300" Width="300"
FontSize="14">
<Window.Resources>
<local:ViewModelScale x:Key="viewModel"/>
<local:ScaleCalcBinding
x:Key="ScaleCalc"
ScaleHeightResult="{Binding ScaleHeight, Mode=OneWayToSource}"
ScaleWidthResult="{Binding ScaleWidth, Mode=OneWayToSource}"
ScanScale="{Binding Text, ElementName=textBox}"
SourceElement="{Binding ElementName=grid, Mode=OneWay}"
TargetElement="{Binding ElementName=border, Mode=OneWay}"
/>
</Window.Resources>
<Window.DataContext>
<Binding Mode="OneWay" Source="{StaticResource viewModel}"/>
</Window.DataContext>
<Grid x:Name="grid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Left" />
<TextBlock HorizontalAlignment="Right" />
<TextBox x:Name="textBox" TextAlignment="Center"
Background="Transparent"
Text="5"/>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border x:Name="border" Background="LightGreen">
<StackPanel>
<TextBlock >
<Run Text="{Binding ActualWidth, ElementName=grid, Mode=OneWay}"/>
<Run Text=", "/>
<Run Text="{Binding ActualHeight, ElementName=grid, Mode=OneWay}"/>
</TextBlock>
<TextBlock >
<Run Text="{Binding ActualWidth, ElementName=border, Mode=OneWay}"/>
<Run Text=", "/>
<Run Text="{Binding ActualHeight, ElementName=border, Mode=OneWay}"/>
</TextBlock>
<TextBlock >
<Run Text="{Binding ScaleWidth}"/>
<Run Text=", "/>
<Run Text="{Binding ScaleHeight}"/>
</TextBlock>
</StackPanel>
</Border>
<GridSplitter Grid.Column="1" ShowsPreview="False" Width="3" Grid.RowSpan="3"
HorizontalAlignment="Center" VerticalAlignment="Stretch" />
<GridSplitter Grid.Row="1" ShowsPreview="False" Height="3" Grid.ColumnSpan="3"
VerticalAlignment="Center" HorizontalAlignment="Stretch" Tag="{Binding Mode=OneWay, Source={StaticResource ScaleCalc}}"/>
</Grid>
</Grid>
</Window>
ViewModel
public class ViewModelScale
{
private double _scaleWidth;
private double _scaleHeight;
// In property setters, recalculate coordinate values ​​from the source collection to the collection for display.
public double ScaleWidth { get => _scaleWidth; set { _scaleWidth = value; RenderScale(); } }
public double ScaleHeight { get => _scaleHeight; set { _scaleHeight = value; RenderScale(); } }
public ObservableCollection<CustomType> ViewCollection { get; } = new ObservableCollection<CustomType>();
public ObservableCollection<CustomType> SourceCollection { get; } = new ObservableCollection<CustomType>();
private void RenderScale()
{
for (int i = 0; i < ViewCollection.Count; i++)
{
ViewCollection[i].X = SourceCollection[i].X * ScaleWidth;
ViewCollection[i].Y = SourceCollection[i].Y * ScaleHeight;
}
}
}

Related

WPF - UserControl constructing performance (very poor)

I have a more complex code on my hand, but to ask this question I am bringing a simpler example of code.
My App is going to iterate throughout all glyphs in a specific font (expected 500 to 5000 glyphs). Each glyph should have a certain custom visual, and some functionality in it. For that I thought that best way to achieve that is to create a UserControl for each glyph.
On the checking I have made, as my UserControl gets more complicated, it takes more time to construct it. Even a simple adding of Style makes a meaningful effect on the performance.
What I have tried in this example is to show in a ListBox 2000 glyphs. To notice the performance difference I put 2 ListBoxes - First is binding to a simple ObservableCollection of string. Second is binding to ObservableCollection of my UserControl.
This is my MainWindow xaml:
<Grid Background="WhiteSmoke">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<ListBox Margin="10" ItemsSource="{Binding MyCollection}"></ListBox>
<ListBox Margin="10" Grid.Row="1" ItemsSource="{Binding UCCollection}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"></ListBox>
</Grid>
On code behind I have 2 ObservableCollection as mentioned:
public static ObservableCollection<string> MyCollection { get; set; } = new ObservableCollection<string>();
public static ObservableCollection<MyUserControl> UCCollection { get; set; } = new ObservableCollection<MyUserControl>();
For the first List of string I am adding like this:
for (int i = 0; i < 2000; i++)
{
string glyph = ((char)(i + 33)).ToString();
string hex = "U+" + i.ToString("X4");
MyCollection.Add($"Index {i}, Hex {hex}: {glyph}");
}
For the second List of MyUserControl I am adding like this:
for (int i = 0; i < 2000; i++)
{
UCCollection.Add(new MyUserControl(i + 33));
}
MyUserControl xaml looks like this:
<Border Background="Black" BorderBrush="Orange" BorderThickness="2" MinWidth="80" MinHeight="80">
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Center" Foreground="White" FontSize="40" Text="{Binding Glyph}"/>
<TextBlock HorizontalAlignment="Center" Foreground="OrangeRed" Text="{Binding Index}" Grid.Row="1"/>
<TextBlock HorizontalAlignment="Center" Foreground="White" Text="{Binding Hex}" Grid.Row="2"/>
</Grid>
</Border>
And code behind of MyUserControl:
public partial class MyUserControl : UserControl
{
private int OrgIndex { get; set; } = 0;
public string Hex => "U+" + OrgIndex.ToString("X4");
public string Index => OrgIndex.ToString();
public string Glyph => ((char)OrgIndex).ToString();
public MyUserControl(int index)
{
InitializeComponent();
OrgIndex = index;
}
}
In order to follow the performance issue I have used Stopwatch. Adding 2000 string items to the first list took 1ms. Adding 2000 UserControls to the second list took ~1100ms. And it is just a simple UserControl, when I add some stuff to it, it takes more time and performance getting poorer. For example if I just add this Style to Border time goes up to ~1900ms:
<Style TargetType="{x:Type Border}" x:Key="BorderMouseOver">
<Setter Property="Background" Value="Black" />
<Setter Property="BorderBrush" Value="Orange"/>
<Setter Property="MinWidth" Value="80"/>
<Setter Property="MinHeight" Value="80"/>
<Setter Property="BorderThickness" Value="2" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" Value="True">
<Setter Property="Background" Value="#FF2A3137" />
<Setter Property="BorderBrush" Value="#FF739922"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
I am not fully familiar with WPF work around, so I will really appreciate your help. Is this a totally wrong way to do this? I have read some posts about it, but could not manage to go through this: here, and here, and here and here and more.
This example full project can be downloaded Here
For your case, you can create DependencyProperty in your user control like so (just an example).
#region DP
public int OrgIndex
{
get => (int)GetValue(OrgIndexProperty);
set => SetValue(OrgIndexProperty, value);
}
public static readonly DependencyProperty OrgIndexProperty = DependencyProperty.Register(
nameof(OrgIndex), typeof(int), typeof(MyUserControl));
#endregion
And other properties can be set as DP or handle in init or loaded event...
Then use your usercontrol in listbox as itemtemplate...
<ListBox
Grid.Row="1"
Margin="10"
ItemsSource="{Binding IntCollection}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling">
<ListBox.ItemTemplate>
<DataTemplate>
<local:MyUserControl OrgIndex="{Binding Path=.}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And in your vm, create simple type list
public static ObservableCollection<int> IntCollection { get; set; } = new ObservableCollection<int>();
for (int i = 0; i < rounds; i++)
{
IntCollection.Add(i + 33);
}
It's quite faster than create a usercontrol list, and you can have your usercontrol and its style as a listviewitem
What solve this issue for now, is following #Andy suggestion to use MVVM approach. It was a bit complicated for me, and had to do some learning around.
What I did:
Cancaled the UserControl.
Created a class GlyphModel. That represents each glyph and it's information.
Created a class GlyphViewModel. That builds an ObservableCollection list.
Set the design for the GlyphModel as a ListBox.ItemTemplate.
So now GlyphModel class, implants INotifyPropertyChanged and looks like this:
public GlyphModel(int index)
{
_OriginalIndex = index;
}
#region Private Members
private int _OriginalIndex;
#endregion Private Members
public int OriginalIndex
{
get { return _OriginalIndex; }
set
{
_OriginalIndex = value;
OnPropertyChanged("OriginalIndex");
}
}
public string Hex => "U+" + OriginalIndex.ToString("X4");
public string Index => OriginalIndex.ToString();
public string Glyph => ((char)OriginalIndex).ToString();
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion INotifyPropertyChanged Members
And GlyphViewModel class looks like this:
public static ObservableCollection<GlyphModel> GlyphModelCollection { get; set; } = new ObservableCollection<GlyphModel>();
public static ObservableCollection<string> StringCollection { get; set; } = new ObservableCollection<string>();
public GlyphViewModel(int rounds)
{
for (int i = 33; i < rounds; i++)
{
GlyphModel glyphModel = new GlyphModel(i);
GlyphModelCollection.Add(glyphModel);
StringCollection.Add($"Index {glyphModel.Index}, Hex {glyphModel.Hex}: {glyphModel.Glyph}");
}
}
In the MainWindow XML I have defined the list with DataTemplate:
<ListBox.ItemTemplate>
<DataTemplate>
<Border Style="{StaticResource BorderMouseOver}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Center" Foreground="White" FontSize="40" Text="{Binding Glyph}" />
<TextBlock HorizontalAlignment="Center" Foreground="OrangeRed" Text="{Binding Index}" Grid.Row="1" />
<TextBlock HorizontalAlignment="Center" Foreground="White" Text="{Binding Hex}" Grid.Row="2" />
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
And for last set the DataContext for the MainWindow:
DataContext = new GlyphViewModel(2000);
It does work, and works very fast even for 4000 glyphs. Hope this is the right way for doing that.

Binding WPF Control Name to different Control

Have style for a series of buttons btn1, btn2, btn3, etc.
Inside the style for each button is a TextBlock for displaying the "Content" of the button, (since the border inside the style covers any content of the button itself).
Now, I would like for the TextBlock name to be tied to the button name. For example - btn1's text block's name would be btn1Txt. The purpose of this would be the end user can assign each button its own text in a settings menu.
Any hints on how I would go about this? I admit I'm relatively new to WPF and bindings.
EDIT:::: WHAT I"VE GOT SO FAR THAT IS WORKING.
On load, the program checks the settings file for the Text for each button. Each button's content is assigned the proper information. Then inside the style, I bind the TextBlock Text to the content of the parent button.
This may not be the normal way of going about it, but it works
Method
List<string> MainButtons = Properties.Settings.Default.MainButtonNames.Cast<string>().ToList();
for (int i = 0; i < MainButtons.Count(); i++)
{
string actualNum = Convert.ToString((i + 1));
var MainButtonFinder = (Button)this.FindName("MainButton" + actualNum);
Console.WriteLine(MainButtonFinder.Name);
MainButtonFinder.Content = MainButtons[i];
Console.WriteLine(MainButtonFinder.Content);
}
Style
<Style x:Key="MainButtonStyle" TargetType="{x:Type Button}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Width" Value="100px"/>
<Setter Property="MinHeight" Value="50"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="20" Height="45" Width="100" Margin="0" Background="#FF99CCFF">
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Content}" HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="LCARS" Foreground="White" Padding="5px" FontSize="18px" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>`
This is the wrong way to go about what you're trying to do. Here's the "right" way to do it. There's a fair amount of boilerplate code here, but you get used to it.
Write a button viewmodel and give your main viewmodel an ObservableCollection of those:
#region ViewModelBase Class
public class ViewModelBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
#endregion INotifyPropertyChanged
}
#endregion ViewModelBase Class
#region MainViewModel Class
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
ButtonItems.Add(new ButtonItemViewModel("First Command", "First Item", () => MessageBox.Show("First Item Executed")));
ButtonItems.Add(new ButtonItemViewModel("Second Command", "Second Item", () => MessageBox.Show("Second Item Executed")));
}
#region ButtonItems Property
public ObservableCollection<ButtonItemViewModel> ButtonItems { get; }
= new ObservableCollection<ButtonItemViewModel>();
#endregion ButtonItems Property
}
#endregion MainViewModel Class
#region ButtonItemViewModel Class
public class ButtonItemViewModel : ViewModelBase
{
public ButtonItemViewModel(String cmdName, String text, Action cmdAction)
{
CommandName = cmdName;
Text = text;
Command = new DelegateCommand(cmdAction);
}
#region Text Property
private String _text = default(String);
public String Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
OnPropertyChanged();
}
}
}
#endregion Text Property
#region CommandName Property
private String _commandName = default(String);
public String CommandName
{
get { return _commandName; }
private set
{
if (value != _commandName)
{
_commandName = value;
OnPropertyChanged();
}
}
}
#endregion CommandName Property
public ICommand Command { get; private set; }
}
#endregion ButtonItemViewModel Class
public class DelegateCommand : ICommand
{
public DelegateCommand(Action action)
{
_action = action;
}
private Action _action;
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_action?.Invoke();
}
}
Make that MainViewModel the DataContext of your Window:
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
And here's how you can put it all together in the XAML:
<Grid>
<StackPanel Orientation="Vertical">
<GroupBox Header="Buttons">
<ItemsControl ItemsSource="{Binding ButtonItems}" HorizontalAlignment="Left">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Margin="2"
MinWidth="80"
Content="{Binding Text}"
Command="{Binding Command}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
<GroupBox Header="Edit Buttons">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox
Grid.Column="0"
Margin="2"
x:Name="ButtonEditorListBox"
ItemsSource="{Binding ButtonItems}"
HorizontalAlignment="Left"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="2"
Text="{Binding CommandName}"
/>
<TextBlock
Margin="2"
Text="{Binding Text, StringFormat=': "{0}"'}"
/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<ContentControl
Grid.Column="1"
Margin="8,2,2,2"
Content="{Binding SelectedItem, ElementName=ButtonEditorListBox}"
>
<ContentControl.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock
Margin="2"
HorizontalAlignment="Stretch"
FontWeight="Bold"
Text="{Binding CommandName, StringFormat={}{0}: }"
/>
<TextBox
Margin="2"
HorizontalAlignment="Stretch"
Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
/>
</StackPanel>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</GroupBox>
</StackPanel>
</Grid>
Back to your question:
Inside the style for each button is a TextBlock for displaying the "Content" of the button, (since the border inside the style covers any content of the button itself).
You're doing styles wrong. Very, very wrong. I can help you fix it if you show me the style.
As I understand what you want is to modify the "text" of the button, it occurs to me that you can do it this way.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBox Text="{Binding ElementName=ButtonTest, Path=Content, UpdateSourceTrigger=PropertyChanged}" ></TextBox>
<Button Name="ButtonTest" Grid.Row="1" Width="100" Height="40">
<Button.ContentTemplate>
<DataTemplate>
<TextBlock Foreground="Blue" Text="{Binding}"></TextBlock>
</DataTemplate>
</Button.ContentTemplate>
</Button>
</Grid>

ErrorTemplate showing for the user control and not the text box within it! How to fix?

There are similar questions to this one on here however I have tried the mentioned solutions to no avail!
Let me just run you through the setup - I have a model which implements IDataErrorInfo, a viewmodel which exposes the model to the view, within the view I have a usercontrol which is simply a labelled textbox, the model properties are binded to the usercontrol's inner textbox via a dependency property... and everything is binding correctly, all validation is fired and the correct errors returned! However, the usercontrol appears to be intercepting the error and thus the errortemplate of the usercontrol is displayed and not the textbox.
So, I know I can stop the usercontrol's error template from being displayed by setting the property to x:Null, however how do I trigger the textbox's error template to be displayed?! I have tried implementing IDataErrorInfo within the usercontrol (as advised by some) and explicitly defining the validation error template within the user control but I just can't get the darn thing to display. At this point I am thinking that the usercontrol is simply intercepting the error, holding onto it and not passing it onto the textbox, hence the errortemplate not being shown as it isn't aware of the error.
I have been pulling my hair out for the past day and really don't want to resort to not using the usercontrol as I know this can be achieved but I really don't know how to fix it! So if there are any wizards out there that can help I would be very grateful!
UserControl XAML:
<UserControl x:Class="PIRS_Client.Control.LabelTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Height="40.541" Width="321.027">
<Grid Height="41" VerticalAlignment="Top" HorizontalAlignment="Left" Width="321">
<StackPanel Orientation="Horizontal" Margin="0,8,50,9">
<Label Content="Label" Height="28" Name="BaseLabel" VerticalAlignment="Top" HorizontalContentAlignment="Right" Width="116" FontSize="11" />
<TextBox Height="22" Width="100" Margin="0,0,0,0" x:Name="BaseTextBox" VerticalContentAlignment="Center" VerticalAlignment="Top" FontSize="11"/>
</StackPanel>
</Grid>
UserControl Code:
public partial class LabelTextBox : UserControl
{
public static readonly DependencyProperty TextBoxTextProperty = DependencyProperty.Register("TextBoxText", typeof(string), typeof(LabelTextBox), new FrameworkPropertyMetadata() { BindsTwoWayByDefault = true });
public LabelTextBox()
{
InitializeComponent();
Binding textBoxText = new Binding("TextBoxText") { Source = this, Mode = BindingMode.TwoWay };
BaseTextBox.SetBinding(TextBox.TextProperty, textBoxText);
}
[Browsable(true)]
public string LabelText
{
get { return BaseLabel.Content.ToString(); }
set
{
BaseLabel.Content = value;
}
}
[Browsable(true)]
public string TextBoxText
{
get { return (string)GetValue(TextBoxTextProperty); }
set { SetValue(TextBoxTextProperty, value); }
}
[Browsable(true)]
public double TextBoxWidth
{
get { return BaseTextBox.Width; }
set
{
BaseTextBox.Width = value;
}
}
}
View - UserControl delcaration:
<control:LabelTextBox HorizontalAlignment="Left" LabelText="Email" TextBoxText="{Binding UpdateSourceTrigger=LostFocus, Path=NewFosterCarerInfo.partner_email, ValidatesOnDataErrors=true, NotifyOnValidationError=true}" TextBoxWidth="120" Margin="190,182,-61,0" VerticalAlignment="Top" Height="41" Width="321"/>
For anyone with my problem, here is the working code
UserControl xaml:
<UserControl x:Class="PIRS_Client.Control.LabelTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Height="40.541" Width="321.027"
x:Name="Parent" Validation.ErrorTemplate="{x:Null}">
<Grid Height="41" VerticalAlignment="Top" HorizontalAlignment="Left" Width="321" DataContext="{Binding ElementName=Parent, ValidatesOnDataErrors=True}">
<StackPanel Orientation="Horizontal" Margin="0,8,50,9">
<Label Content="Label" Height="28" Name="BaseLabel" VerticalAlignment="Top" HorizontalContentAlignment="Right" Width="116" FontSize="11" />
<TextBox Height="22" Text="{Binding Path=TextBoxText, ValidatesOnDataErrors=True}" Width="100" Margin="0,0,0,0" x:Name="BaseTextBox" VerticalContentAlignment="Center" VerticalAlignment="Top" FontSize="11"/>
</StackPanel>
</Grid>
UserControl code behind:
public partial class LabelTextBox : UserControl, IDataErrorInfo
{
public LabelTextBox()
{
InitializeComponent();
}
public static readonly DependencyProperty TextBoxTextProperty =
DependencyProperty.Register(
"TextBoxText",
typeof(string),
typeof(LabelTextBox),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
#region IDataErrorInfo Members
public string Error
{
get
{
if (Validation.GetHasError(this))
return string.Join(Environment.NewLine, Validation.GetErrors(this).Select(e => e.ErrorContent));
return null;
}
}
public string this[string columnName]
{
get
{
// use a specific validation or ask for UserControl Validation Error
if (Validation.GetHasError(this))
{
var error = Validation.GetErrors(this).FirstOrDefault(e => ((BindingExpression)e.BindingInError).TargetProperty.Name == columnName);
if (error != null)
return error.ErrorContent as string;
}
return null;
}
}
#endregion
[Browsable(true)]
public string LabelText
{
get { return BaseLabel.Content.ToString(); }
set { BaseLabel.Content = value; }
}
[Browsable(true)]
public string TextBoxText
{
get { return (string)GetValue(TextBoxTextProperty); }
set {
SetValue(TextBoxTextProperty, value);
}
}
[Browsable(true)]
public double TextBoxWidth
{
get { return BaseTextBox.Width; }
set { BaseTextBox.Width = value; }
}
}
Using the UserControl:
<control:LabelTextBox HorizontalAlignment="Left" LabelText="Email" TextBoxText="{Binding Path=NewFosterCarerInfo.partner_email, ValidatesOnDataErrors=true}" TextBoxWidth="120" Margin="190,182,-61,0" VerticalAlignment="Top" Height="41" Width="321"/>
And in case you wanted a nice Validation.ErrorTemplate:
`<Style TargetType="{x:Type TextBox}">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="0,2,40,2" />
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="true">
<Border Background="Red" DockPanel.Dock="right" Margin="5,0,0,0" Width="20" Height="20" CornerRadius="10"
ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
<TextBlock Text="!" VerticalAlignment="center" HorizontalAlignment="center" FontWeight="Bold" Foreground="white">
</TextBlock>
</Border>
<AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
<Border BorderBrush="red" BorderThickness="1" />
</AdornedElementPlaceholder>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>`

Dependency Property inner Binding in custom control

I'm trying to bind the ItemsSource property of an ItemsControl contained in my UserControl (a group of radiobuttons) to a property of the same UserControl. This should be driven by the DependencyProperty when the UserControl is used. But I am not able to make it work, if instead I hardcode the control name the assignment works correctly.
This is the class :
public partial class GroupListBox : UserControl
{
public GroupListBox()
{
InitializeComponent();
}
private List<RadioButton> _itemsList = new List<RadioButton>();
public List<RadioButton> ItemsList
{
get { return _itemsList; }
set
{
_itemsList = value;
}
}
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(GroupListBox), new FrameworkPropertyMetadata(null, OnItemsSourceChanged));
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (GroupListBox)d;
var grpName = control.GetHashCode().ToString(); // used to generate a unique GroupName
var oldValue = (IEnumerable)e.OldValue;
var newValue = (IEnumerable)e.NewValue;
if ((e.NewValue == null) && !BindingOperations.IsDataBound(d, ItemsSourceProperty))
{
control.ItemsList.Clear();
}
else
{
var radioItems = (from object item in newValue select new RadioButton() { Content = item, GroupName = grpName }).ToList();
//control.container.ItemsSource = radioItems; // *** this works
control.ItemsList = radioItems; // *** this doesn`t
}
}
this is the XAML
<Border x:Name="brdMain">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="txtTitle" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="0" Text="" />
<Image x:Name="imgImage" Grid.Column="0" Grid.Row="1" />
<ScrollViewer x:Name="scroll" Grid.Row="1" Grid.Column="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" CanContentScroll="True">
<!--This is the control I want to bind-->
<ItemsControl x:Name="container" ItemsSource="{Binding Path=ItemsList}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Grid>
</Border>
used like this:
<cust:GroupListBox ItemsSource="{Binding SymmetryAxis}"></cust:GroupListBox>
I'm sure there is something wrong with my control binding but I don`t know what.
Any clue?
EDIT :
I did some little change in the XAML :
now it looks like this :
<ItemsControl Name="container" Width="{Binding ElementName=scroll, Path=ViewportWidth}" ItemsSource="{Binding Path=ItemsSource, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:GroupListBox}}}" ItemTemplate="{StaticResource ItemPanelDatatemplate}" />
and this is the DataTemplate :
<DataTemplate DataType="{x:Type ItemsPanelTemplate}" x:Key="ItemPanelDatatemplate">
<RadioButton Content="{Binding}" GroupName="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ItemsControl}, Path=Name}"></RadioButton> </DataTemplate>
This anyway gives problems with the GroupName because it`s always empty and then all the RadioButtons act like if they are in a single group.
How can I obtain the same result like in the code behind (for example assigning a unique name for each group?

Can't create dependency property in a UserControl in a Windows Runtime Component library

I wanted to created data bindable property inside a user control. And this user control contains inside a "Windows Runtime Component" project. I used below code to create property.
public MyItem CurrentItem
{
get { return (MyItem)GetValue(CurrentItemProperty); }
set { SetValue(CurrentItemProperty, value); }
}
// Using a DependencyProperty as the backing store for CurrentItem.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty CurrentItemProperty =
DependencyProperty.Register("CurrentItem", typeof(MyItem), typeof(CollapseUserControl), new PropertyMetadata(null));
When I compile the project I get below error.
Type 'HierachyLib.CollapseUserControl' contains externally visible field 'HierachyLib.CollapseUserControl.CurrentItemProperty'. Fields can be exposed only by structures.
Update 1 - Source code for whole class
public sealed partial class CollapseUserControl : UserControl, IHierarchyHeightFix
{
public MyItem CurrentItem
{
get { return (MyItem)GetValue(CurrentItemProperty); }
set { SetValue(CurrentItemProperty, value); }
}
// Using a DependencyProperty as the backing store for CurrentItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CurrentItemProperty =
DependencyProperty.Register("CurrentItem", typeof(MyItem), typeof(CollapseUserControl), new PropertyMetadata(null));
Boolean viewState = true;
public CollapseUserControl()
{
this.DataContext = CurrentItem;
this.InitializeComponent();
this.Loaded += CollapseUserControl_Loaded;
}
void CollapseUserControl_Loaded(object sender, RoutedEventArgs e)
{
LoadData();
if (this.Height.Equals(double.NaN))
{
this.Height = 50;
}
//this.Height = 50;
//this.Width = double.NaN;
}
private void LoadData()
{
if (CurrentItem != null)
{
if (CurrentItem.IsValueControl)
{
ChildItemContainer.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
ValueItem.Visibility = Windows.UI.Xaml.Visibility.Visible;
ValueItem.Text = CurrentItem.Value;
}
else
{
ChildItemContainer.Visibility = Windows.UI.Xaml.Visibility.Visible;
ValueItem.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
ChildItems.ItemsSource = CurrentItem.Childs;
//foreach (MyItem item in CurrentItem.Childs)
//{
// CollapseUserControl control = new CollapseUserControl();
// control.CurrentItem = item;
// ChildItems.Items.Add(control);
//}
ChildItems.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}
}
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
if (viewState)
{
ChildItems.Visibility = Windows.UI.Xaml.Visibility.Visible;
//show.Begin();
}
else
{
//hide.Begin();
ChildItems.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}
viewState = !viewState;
}
private void hide_Completed_1(object sender, object e)
{
ChildItems.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}
private void show_Completed_1(object sender, object e)
{
}
}
Update 2 : XAML Code
<UserControl
x:Class="HierachyLib.CollapseUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:HierachyLib"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<DataTemplate x:Key="ReviewsItemsTemplate">
<StackPanel Margin="0,0,0,20">
<TextBlock Text="TEST" />
<TextBlock Text="TEST"/>
</StackPanel>
</DataTemplate>
<ItemsPanelTemplate x:Key="ReviewsItemsPanelTemplate">
<StackPanel Margin="0,0,0,0" Width="Auto"/>
</ItemsPanelTemplate>
</UserControl.Resources>
<!--xmlns:my="clr-namespace:HierarchyCollapse"-->
<Grid>
<Grid Name="ChildItemContainer">
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Margin="0,0,70,0" Fill="Transparent" Canvas.ZIndex="4"/>
<ListView HorizontalContentAlignment="Stretch" Background="#FF6599CD" CanDragItems="False" CanReorderItems="False" Grid.Row="0"
ScrollViewer.VerticalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollMode="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.HorizontalScrollMode="Disabled">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Height="40">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<TextBlock Text="Sample Text" FontSize="17" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10,10,0,0" Canvas.ZIndex="10"/>
<Image PointerPressed="Button_Click_1" Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Center" Source="Assets/arrw_right.png" Stretch="None" Margin="0,0,10,0"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListViewItem />
</ListView>
<ListView Grid.Row="1"
IsHitTestVisible="True"
CanDragItems="True"
CanReorderItems="True"
AllowDrop="True"
Name="ChildItems"
SelectionMode="None"
IsItemClickEnabled="False">
<ListView.ItemTemplate>
<DataTemplate>
<local:CollapseUserControl CurrentItem="{Binding RelativeSource={RelativeSource Self}}" />
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
<TextBlock Name="ValueItem" Margin="0,0,0,0" Height="40" FontSize="36"></TextBlock>
</Grid>
New error I get:
Failed to create a 'Windows.UI.Xaml.PropertyPath' from the text ''.
Error comes from <local:CollapseUserControl CurrentItem="{Binding RelativeSource={RelativeSource Self}}" />.
It seems like you can mark your property as internal and then it starts working...
internal static readonly DependencyProperty CurrentItemProperty...
EDIT*
A better approach that seems to be what the platform controls do is to have an actual CLR property that exposes the DependencyProperty object - something like this:
private static readonly DependencyProperty CurrentItemPropertyField =
DependencyProperty.Register/RegisterAttached(...);
internal static DependencyProperty CurrentItemProperty
{
get
{
return CurrentItemPropertyField;
}
}
This allows tools such as Blend to discover the properties.
You can use an automatic property setting the default value (from c# 5) like this:
public static DependencyProperty CurrentItemPropertyField { get; } =
DependencyProperty.Register("Value", typeof(string), typeof(MyControl), new PropertyMetadata("{No Value}"));
Works also for UWP applications

Categories

Resources