Control.AddRange(...) is slow - c#

Project:
I have a parent panel which holds a ComboBox and FlowLayoutPanel. The FlowLayoutPanel holds a variable number of child panels (a custom control that inherits from UserControl). Each child panel contains some labels, two ComboBoxes, a button, and a DataGridView with 3 ComboBox columns and a button column. The DataGridView may have 1-6 rows. The FlowLayoutPanel is populated with child panels when an item is selected from the ComboBox on the parent panel.
Problem:
Populating the FlowLayoutPanel with about 50 child panels takes about 2.5 seconds. Specifically, I've determined that the call to FlowLayoutPanel.Controls.AddRange() is the culprit.
Relevant Code: I can't post all of my code here (too much code plus parts of it are confidential), but I'll do my best to explain what is happening.
Parent Panel:
private void displayInformation(Suite suite)
{
this.SuspendLayout();
// Get dependencies.
List<SuiteRange> dependents = new List<SuiteRange>(suite.dependencies.Keys);
dependents.Sort(SuiteRange.Compare);
// Create a ChildPanel for each dependent.
List<ChildPanel> rangePanels = new List<ChildPanel>();
foreach (SuiteRange dependent in dependents)
{
ChildPanel sdp = new ChildPanel();
sdp.initialize(initialSuite.name, dataAccess);
sdp.displayInformation(dependent, suite.dependencies[dependent]);
rangePanels.Add(sdp);
}
// Put the child panels in the FlowLayoutPanel.
flpDependencyGroups.SuspendLayout();
// Takes ~2.5 seconds
flpDependencyGroups.Controls.AddRange(rangePanels.ToArray());
flpDependencyGroups.ResumeLayout();
// Takes ~0.5 seconds
updateChildPanelSizes();
this.ResumeLayout();
}
Things I've tried:
Call SuspendLayout() / ResumeLayout() on the parent panel and/or FlowLayoutPanel. Minimal performance increase (~0.2 seconds).
Use Control.FlatStyle.Flat on ComboBoxes, Buttons, and DataGridView columns. Minimal performance increase (~0.1 seconds).
Verified that none of my controls use a transparent background color.
Set ChildPanel.DoubleBuffered and ParentPanel.DoubleBuffered to true.
Remove the FlowLayoutPanel from its parent before calling AddRange() and re-adding it after.
Things that might be relevant:
The panels and controls use anchors (as opposed to autosize or dock).
My controls are manually populated and do not use the DataSource property.
EDIT: Solution:
#HighCore's answer is the correct solution. Unfortunately I won't be implementing it at this time (it could happen down the road) because I found a workaround. The workaround doesn't really solve the problem, just masks it, hence why I'm not posting this as an answer. I discovered that the form loads in half the time if the Dependencies tab isn't on top (i.e. the Product Lists tab is selected). This reduces loading time to about 1 second, which is acceptable. When data is being loaded and the Dependencies tab is on top, I switch to the Product Lists tab, throw up a dark grey box over the tab control that says "Loading..." in the middle, load the data, and then switch back to the Dependencies tab.
Thanks all for your comments and suggestions, it was greatly appreciated.

Posting this answer because the OP requested it:
This is how you'd do something like that in WPF:
<UserControl x:Class="WpfApplication7.ListBoxSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DockPanel>
<Button Content="Load" Click="Load_Click" DockPanel.Dock="Top"/>
<ListBox ItemsSource="{Binding}"
HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="LightGray" BorderThickness="1" Padding="5"
Background="#FFFAFAFA">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Dependent Versions" FontWeight="Bold"
Grid.ColumnSpan="2" HorizontalAlignment="Center"/>
<TextBlock Text="From:" FontWeight="Bold"
Grid.Row="1" HorizontalAlignment="Center"/>
<TextBlock Text="To (exclusive):" FontWeight="Bold"
Grid.Row="1" Grid.Column="1" HorizontalAlignment="Center"/>
<ComboBox SelectedItem="{Binding From}"
ItemsSource="{Binding FromOptions}"
Grid.Row="2" Margin="5"/>
<ComboBox SelectedItem="{Binding To}"
ItemsSource="{Binding ToOptions}"
Grid.Row="2" Grid.Column="1" Margin="5"/>
<DataGrid ItemsSource="{Binding ChildItems}"
AutoGenerateColumns="False" CanUserAddRows="False"
Grid.Column="2" Grid.RowSpan="4">
<DataGrid.Columns>
<DataGridTextColumn Header="XXXX" Binding="{Binding XXXX}"/>
<DataGridTextColumn Header="Dependee From" Binding="{Binding DependeeFrom}"/>
<DataGridTextColumn Header="Dependee To" Binding="{Binding DependeeTo}"/>
<DataGridTemplateColumn Width="25">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="X"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button Content="Delete"
Grid.Column="3"
HorizontalAlignment="Right" VerticalAlignment="Top"/>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</UserControl>
Code Behind (only boilerplate to support the example)
public partial class ListBoxSample : UserControl
{
public ListBoxSample()
{
InitializeComponent();
}
public void LoadData()
{
Task.Factory.StartNew(() =>
{
var list = new List<DataItem>();
for (int i = 0; i < 100000; i++)
{
var item = new DataItem()
{
From = "1",
To = "2",
ChildItems =
{
new ChildItem()
{
DependeeFrom = i.ToString(),
DependeeTo = (i + 10).ToString(),
XXXX = "XXXX"
},
new ChildItem()
{
DependeeFrom = i.ToString(),
DependeeTo = (i + 10).ToString(),
XXXX = "XXXX"
},
new ChildItem()
{
DependeeFrom = i.ToString(),
DependeeTo = (i + 10).ToString(),
XXXX = "XXXX"
}
}
};
list.Add(item);
}
return list;
}).ContinueWith(t =>
{
Dispatcher.Invoke((Action) (() => DataContext = t.Result));
});
}
private void Load_Click(object sender, System.Windows.RoutedEventArgs e)
{
LoadData();
}
}
Data Items:
public class DataItem
{
public List<ChildItem> ChildItems { get; set; }
public List<string> FromOptions { get; set; }
public List<string> ToOptions { get; set; }
public string From { get; set; }
public string To { get; set; }
public DataItem()
{
ChildItems = new List<ChildItem>();
FromOptions = Enumerable.Range(0,10).Select(x => x.ToString()).ToList();
ToOptions = Enumerable.Range(0, 10).Select(x => x.ToString()).ToList();
}
}
public class ChildItem
{
public string XXXX { get; set; }
public string DependeeFrom { get; set; }
public string DependeeTo { get; set; }
}
Then you put that in an existing winforms UI using an ElementHost:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var elementHost = new ElementHost
{
Dock = DockStyle.Fill,
Child = new ListBoxSample()
};
Controls.Add(elementHost);
}
}
Result:
Notice that I added 100,000 records. Still, response time (both when scrolling and interacting with the UI) is immediate due to WPF's built in UI Virtualization.
Also notice that I'm using DataBinding which removes the need to manipulate UI elements in procedural code. This is important because the WPF Visual Tree is a complex structure, and DataBinding is the preferred approach in WPF always.
Also notice by resizing the form that the UI is completely resolution independent. You can customize it further by making the ComboBoxes fixed and having the DataGrid stretch to the remaining space. See WPF Layouts.
WPF Rocks. - see how much you can achieve with so little code, and without spending lots of $$$ in third party controls. You should really forget winforms forever.
You will need to target .Net 3.0 at a minimum, but 4.0/4.5 is highly recommended because WPF had several issues in earlier versions, which were fixed in 4.0.
Make sure you reference PresentationCore.dll, PresentationFramework.dll, WindowsBase.dll, System.Xaml.dll and WindowsFormsIntegration.dll, all of which belong to the .Net Framework itself (no 3rd parties)

Related

Wpf Xaml DataTemplate. Cannot find source for binding with reference 'ElementName=proxy'

We're in the process of converting a SL5+RIA service to a WPF front and WCF service as a temporary solution.
The old SL solution is maintained with VS 2015 14.0.25431.01. It uses SL5 and .NET 4.0. Other noteworthy libraries are Telerik SL controls 2011.2.0920 and Galasoft MVVMLight SL5 4.1.26.
The WPF solution is made using VS 2019 16.11.15, NET 4.8, Telerik WPF controls 2021.1.325 and Galasoft MVVMLight WPF45 4.1.26.
We have a custom control in which we use bars projected on a timescale to visualise timespans when items have different statusses through a period of time. It's a listbox of the different items and each line is filled with buttons to create the visual of a timebar accross a timeline.
To calculate the widths of the buttons, the total width in both timespan and pixels is used to calculate the comparative width in pixels for each button on each line.
Getting the total width in pixels is giving us problems.
The XAML Datatemplate of the control
<!--Timelines-->
<Grid Grid.Row="0" Grid.Column="1" x:Name="SomeGrid" Background="#FFD3D3D3">
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="10" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!-- TEXTBLOCK TO TRIGGER HELPER CLASS ActualSizePropertyProxy -->
<TextBlock Visibility="Collapsed" x:Name="tb1" Text="{Binding ActualWidthValue, ElementName=proxy}" />
<TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" Text="{Binding ProgramViewmodel.TimelineAreaStart, Source={StaticResource Locator}, StringFormat=\{0:dd-MM-yyyy\}}" />
<TextBlock Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" Text="{Binding ProgramViewmodel.TimelineAreaEnd, Source={StaticResource Locator}, StringFormat=\{0:dd-MM-yyyy\}}" />
<!-- Vertical line under the start date -->
<TextBlock Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" Text="|" Margin="30 0 0 0" />
<ListBox x:Name="ListBoxTimeFreq"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Background="#FFD3D3D3"
ItemsSource="{Binding Converter={StaticResource TimefrequentiesFromPAPConverter}}"
SelectionMode="Single"
ItemTemplate="{StaticResource TimeFreqTimelineItemTemplate}">
<ListBox.Resources>
<helpers:ActualSizePropertyProxy Element="{Binding ElementName=ListBoxTimeFreq}" x:Name="proxy" />
</ListBox.Resources>
</ListBox>
</Grid>
It's a simple grid showing the start date, end date and a | marking the start date. Then there is the listbox ListBoxTimeFreq below it that uses the TimeFreqTimelineItemTemplate datatemplate to fill in bars.
And above it all but collapsed there is a textblock that spreads itself across the available space for listbox items, across the available space for timelines. Whenever it's on screen, it triggers the getter on a property ActualWidthValue in a class ActualSizePropertyProxy.
The ActualSizePropertyProxy class
public class ActualSizePropertyProxy : FrameworkElement, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public FrameworkElement Element
{
get { return (FrameworkElement)GetValue(ElementProperty); }
set { SetValue(ElementProperty, value); }
}
public double ActualHeightValue
{
get { return Element == null ? 0 : Element.ActualHeight; }
}
public double ActualWidthValue
{
get
{
if (null != Element)
{
if (Element.ActualWidth != 0.0)
{
ViewModelLocator.ProgramViewmodel.PixelwidthTimelineArea = Element.ActualWidth - 90; // because of CbxValidateStatus, CbxEndOfLine & cbxAddScopeMultiplePAPs
ViewModelLocator.ProgramViewmodel.PixelwidthTimelineAreaScopes = Element.ActualWidth - 86; // because of the border
ViewModelLocator.ProgramViewmodel.Counter = 0;
ViewModelLocator.ProgramViewmodel.CounterScopes = 0;
}
return Element.ActualWidth;
}
else
{
ViewModelLocator.ProgramViewmodel.PixelwidthTimelineArea = 0;
ViewModelLocator.ProgramViewmodel.PixelwidthTimelineAreaScopes = 0;
return 0;
}
}
}
public static readonly DependencyProperty ElementProperty =
DependencyProperty.Register("Element", typeof(FrameworkElement), typeof(ActualSizePropertyProxy),
new PropertyMetadata(null, OnElementPropertyChanged));
private static void OnElementPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ActualSizePropertyProxy)d).OnElementChanged(e);
}
private void OnElementChanged(DependencyPropertyChangedEventArgs e)
{
FrameworkElement oldElement = (FrameworkElement)e.OldValue;
FrameworkElement newElement = (FrameworkElement)e.NewValue;
newElement.SizeChanged += new SizeChangedEventHandler(Element_SizeChanged);
if (oldElement != null)
{
oldElement.SizeChanged -= new SizeChangedEventHandler(Element_SizeChanged);
}
NotifyPropChange();
}
private void Element_SizeChanged(object sender, SizeChangedEventArgs e)
{
NotifyPropChange();
}
private void NotifyPropChange()
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("ActualWidthValue"));
PropertyChanged(this, new PropertyChangedEventArgs("ActualHeightValue"));
}
}
}
The getter on ActualWidthValue reads out the ActualWidth of the element and stores the value in a viewmodel. Later having the width in that viewmodel is crucial because the converters calculating the sizes for the timebars use that value from the viewmodel.
In the old SL solution this setup causes the PixelwidthTimelineArea in the viewmodel to store a real value. When the application's width is around 1200, the ActualWidthValue return around 800.
But in the new WPF solution, the getter on ActualWidthValue is never triggered. None of the properties or events get triggered in ActualSizePropertyProxy because the binding goes wrong in the XAML in the WPF version.
And we can't figure out the right fix that is needed to make it work in WPF. It wants the source but you can't bind with Source when using ElementName.
Cannot find source for binding with reference 'ElementName=proxy'. BindingExpression:Path=ActualWidthValue; DataItem=null; target element is 'TextBlock' (Name='tb1'); target property is 'Text' (type 'String')
Cannot find source for binding with reference 'ElementName=ListBoxTimeFreq'. BindingExpression:(no path); DataItem=null; target element is 'ActualSizePropertyProxy' (Name='proxy'); target property is 'Element' (type 'FrameworkElement')
It's probably a very easy solution and our heads have gone mush trying to overthink it.

Implement data virtualisation in a UWP ListView without duplicating items

I have a large ListView which is largely made InkCanvas objects, it turns out that ListView implements data virtualisation to "cleverly" unload and load items in the view depending on the visible items in the view. The problem with this is that many times the ListView caches items and when a new item is added it essentially copy items already added in the view. So in my case, if the user adds a stroke to an Inkcanvas and then adds a new InkCanvas to the ListView, the new canvas contains the strokes from the previous canvas. As reported here this is because of the data virtualisation. My ListView is implemented as follows:
<Grid HorizontalAlignment="Stretch">
<ListView x:Name="CanvasListView" IsTapEnabled="False"
IsItemClickEnabled="False"
ScrollViewer.ZoomMode="Enabled"
ScrollViewer.HorizontalScrollMode="Enabled"
ScrollViewer.VerticalScrollMode="Enabled"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Visible"
HorizontalAlignment="Stretch">
<!-- Make sure that items are not clickable and centered-->
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<local:CanvasControl Margin="0 2"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
MinWidth="1000" MinHeight="100" MaxHeight="400"
Background="LightGreen"/>
<Grid HorizontalAlignment="Stretch" Background="Black" Height="2"></Grid>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
<InkToolbar x:Name="inkToolbar"
VerticalAlignment="Top"
Background="LightCoral"/>
<StackPanel HorizontalAlignment="Right">
<Button x:Name="AddButton" Content="Add Page" Click="Button_Click"/>
<TextBlock x:Name="PageCountText" />
</StackPanel>
</StackPanel>
</Grid>
A full example can be found here and here is a video of the issue.
Indeed if I turn off data virtualisation (or switch to an ItemsControl) everything works brilliantly. The problem however is that with a very large list, this approach has a heavy impact on performance (with 60+ InkCanvas controls the app just crashes). So is there a way to retain data virtualisation while avoiding the duplication of items? I have tried with VirtualizationMode.Standard but items are still duplicated.
To solve this problem, we must first understand why this problem occurs.
ListView has a reuse container inside, it will not endlessly create new list items, but will recycle.
In most cases, such recycling is not a problem. But it's special for InkCanvas.
InkCanvas is a stateful control. When you draw on InkCanvas, the handwriting is retained and displayed on the UI.
If your control is a TextBlock, this problem does not occur, because we can directly bind the value to TextBlock.Text, but for the Stroke of InkCanvas, we cannot directly bind, which will cause the so-called residue.
So in order to avoid this, we need to clear the state, that is, every time the InkCanvas is created or reloaded, the strokes in the InkCanvas are re-rendered.
1. Create a list for saving stroke information in ViewModel
public class ViewModel : INotifyPropertyChanged
{
// ... other code
public List<InkStroke> Strokes { get; set; }
public ViewModel()
{
Strokes = new List<InkStroke>();
}
}
2. Change the internal structure of CanvasControl
xaml
<Grid>
<InkCanvas x:Name="inkCanvas"
Margin="0 2"
MinWidth="1000"
MinHeight="300"
HorizontalAlignment="Stretch" >
</InkCanvas>
</Grid>
xaml.cs
public sealed partial class CanvasControl : UserControl
{
public CanvasControl()
{
this.InitializeComponent();
// Set supported inking device types.
inkCanvas.InkPresenter.InputDeviceTypes =
Windows.UI.Core.CoreInputDeviceTypes.Mouse |
Windows.UI.Core.CoreInputDeviceTypes.Pen;
}
private void StrokesCollected(InkPresenter sender, InkStrokesCollectedEventArgs args)
{
if (Data != null)
{
var strokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes().ToList();
Data.Strokes = strokes.Select(p => p.Clone()).ToList();
}
}
public ViewModel Data
{
get { return (ViewModel)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(ViewModel), typeof(CanvasControl), new PropertyMetadata(null,new PropertyChangedCallback(Data_Changed)));
private static void Data_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(e.NewValue!=null && e.NewValue is ViewModel vm)
{
var strokes = vm.Strokes.Select(p=>p.Clone());
var instance = d as CanvasControl;
instance.inkCanvas.InkPresenter.StrokesCollected -= instance.StrokesCollected;
instance.inkCanvas.InkPresenter.StrokeContainer.Clear();
try
{
instance.inkCanvas.InkPresenter.StrokeContainer.AddStrokes(strokes);
}
catch (Exception)
{
}
instance.inkCanvas.InkPresenter.StrokesCollected += instance.StrokesCollected;
}
}
}
In this way, we can keep our entries stable.

C# WPF - variable value won't display on Textboxes (DataContext?)

I got a really tricky and annoying problem with my C# WPF Application. I guess it's not a big deal for a good programmer to solve it, but I don't know how to fix it yet. For school, I have to program an application which depicts a process. So I get Data by an XML-File, have to calculate some values, display them for User Interaction etc. and at the end the output is again a file, which can be processed further.
For that, I got different UserControls, which depicts different modules for example, one for the Data Import, the other one for calculating and displayng values and so on. The Main Window is like the free space or the place-maker on which the different modules are loaded depending on where we are in the process.
My problem now is that the values I calculate in my UserControl won't display in my UI respectively my application and I don't really know why. 0 is the only value which is transferred to the application. Curious about it, is that in the Debugger the values are correct, but in the display itself there is only a 0.
Ok, so I show you now the code of the different files (I'm not the best programmer, so maybe the code is sometimes a bit dirty).
I got a Main UserControl, let's call it UC_Main, and in this UserControl you can switch between 3 different other UserControls depending on which Radiobutton in the UC_Main is checked. (The UC_Main is always displayed, because in this there are only the 3 radio buttons and underneath is a big free space, where the different UserControls 1, 2 and 3 are loaded).
UC_Main.xaml
<UserControl.Resources>
<DataTemplate x:Name="UC1_Template" DataType="{x:Type local:UC1}">
<local:UC1 DataContext="{Binding}" />
</DataTemplate>
<DataTemplate x:Name="UC2_Template" DataType="{x:Type local:UC2}">
<local:UC2 DataContext="{Binding}" />
</DataTemplate>
<DataTemplate x:Name="UC3_Template" DataType="{x:Type local:UC3}">
<local:UC3 DataContext="{Binding}" />
</DataTemplate>
</UserControl.Resources>
<Border Padding="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!-- In the First Row there are the radio buttons in the second the
different UserControls 1, 2 or 3 -->
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<materialDesign:ColorZone Mode="PrimaryMid" Width="400" HorizontalAlignment="Left">
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="2">
<RadioButton x:Name="UC1_radiobutton" Checked="UC1_radiobutton_Checked"
Style="{StaticResource MaterialDesignTabRadioButton}"
Margin="4"
IsChecked="True"
Content="UserControl1" />
<RadioButton x:Name="UC2_radiobutton" Checked="UC2_radiobutton_Checked"
Style="{StaticResource MaterialDesignTabRadioButton}"
Margin="4"
IsChecked="False"
Content="UserControl2" />
<RadioButton x:Name="UC3_radiobutton" Checked="UC3_radiobutton_Checked"
Style="{StaticResource MaterialDesignTabRadioButton}"
Margin="4"
IsChecked="False"
Content="UserControl3" />
</StackPanel>
</materialDesign:ColorZone>
<ContentControl Grid.Row="1" Content="{Binding}" />
</Grid>
</Border>
</UserControl>
UC_Main.xaml.cs
public partial class UC_Main : UserControl
{
public UC_Main()
{
InitializeComponent();
}
private void UC1_radiobutton_Checked(object sender, RoutedEventArgs e)
{
DataContext = new UC1();
}
private void UC2_radiobutton_Checked(object sender, RoutedEventArgs e)
{
DataContext = new UC2();
}
private void UC3_radiobutton_Checked(object sender, RoutedEventArgs e)
{
DataContext = new UC3();
}
}
}
To keep it simple, I'll only show you the Code of UserControl 1, because UC 2 and 3 are pretty the same beside other variables or values.
UC1.xaml
<Border Padding="10">
<StackPanel>
<!-- To keep the example simple, I got 1 Row and 2 Colums; in each
is one TextBox -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<TextBox x:Name="TextBox1" Grid.Column="0" IsTabStop="False"
Text="{Binding Path=variable1, Mode=TwoWay}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextAlignment="Center"
Height="25"
Width="85"
Foreground="DarkGray"
IsReadOnly="True" />
<TextBox x:Name="TextBox2" Grid.Column="1" IsTabStop="False"
Text="{Binding Path=variable2, Mode=TwoWay}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextAlignment="Center"
Height="25"
Width="85"
Foreground="DarkGray"
IsReadOnly="True" />
</Grid>
</StackPanel>
</Border>
</UserControl>
UC_1.xaml.cs
public partial class UC1 : UserControl
{
public MainWindow Speaker;
public ValueStore vs;
public UC1()
{
InitializeComponent();
Speaker = MainWindow.AppWindow;
vs = new ValueStore();
DataContext = vs;
}
public void calc_data()
{
// I get the data from the data import (XML-File), which is saved in
// a dictionary (strings), converting them to int (so I can do some
// math operations) and save them in my variable.
// UC_Other is a UserControl, where the data import happens
// dict_other is the dictionary, where the data from the import is
// saved
vs.variable1 =
Convert.ToInt32(MainWindow.AppWindow.UC_other.dict_other["0"].Amount);
vs.variable2 =
Convert.ToInt32(MainWindow.AppWindow.UC_other.dict_other["1"].Amount);
}
I call the function calc_data() in an UserControl before, so the data gets calculated and saved in my variables before my UserControl shows up. I declare a new public instance of my UC1 and call the function via UC1.calc_data(); (which is linked to a Button, that is loading my UC_Main).
ValueStore.cs
public class ValueStore : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
private int _variable1;
public int variable1
{
get { return _variable1; }
set { _variable1 = value; OnPropertyChanged("variable1"); }
}
private int _variable2;
public int variable2
{
get { return _variable2; }
set { _variable2 = value; OnPropertyChanged("variable2"); }
}
When I look in the debugger after the method calc_data() is called, the values are correct saved in my ValueStore instance and the TextBoxes are showing me in the Debugger that the correct value is in there (the Debugger says "Name: TextBox1" and "Value: {System.Windows.Controls.TextBox: 100}"; 100 is the value I got from the dictionary), but in my application itself there is only the value 0 displayed.
What I don't really understand is, when I change the type from variable1 to string in my ValueStore.cs and save it in my variable in the method calc_data()(without Convert.ToInt32), it doesn't even show a 0 any more in my application, but in the debugger there is still the value "100".
There are a few things here, but my best guess why your debugging-values are correct while none are updated to the GUI is here:
public partial class UC_Main : UserControl
{
public UC_Main()
{
InitializeComponent();
}
private void UC1_radiobutton_Checked(object sender, RoutedEventArgs e)
{
DataContext = new UC1();
}
private void UC2_radiobutton_Checked(object sender, RoutedEventArgs e)
{
DataContext = new UC2();
}
private void UC3_radiobutton_Checked(object sender, RoutedEventArgs e)
{
DataContext = new UC3();
}
}
You are creating new instances of these classes of the code-behind to the usercontrols. But the usercontrol objects are already created by the UC_Main.xaml so during runtime, you have two objects for example of the UC1 class, one which is your bound to your GUI and one where you store and update your values. The one you see on your GUI doesn't get any values updates, which is why you aren't seeing anything.
I currently can't test the code myself, but from what I can see that is where the issue lies.
Furthermore it is a bit confusing to me, why you are using databinding for code-behind.
(You are using the code-behind of the UC-classes as datacontext for the main class, which is....weird ;) I think in your case no databinding whatsoever is really needed, however if you want to do stuff with databinding you should probably read up on MVVM)

UWP update a itemscontrol with an array that keeps updating

I have an array that keeps changing its values, because of this I want to have the apps UI refreshing every time the array's values do. I have this bound with an itemsControl. I can show the first array's values but then I can't update them I have tried .items.Clear() but its not working. Here are snippets of the .xaml and the xaml.cs. I actually took the code of the .xaml from a question from this site.
.xaml
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBox Text="Testing" IsReadOnly="True"></TextBox>
<ItemsControl x:Name="itemsControl"
ItemsSource="{Binding itemsControl}"
FontSize="24">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="Auto"
Margin="0 12"
HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Column="0"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock Name="txtblk0" Text="{Binding}" />
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
.xaml.cs
String c = (new String(cArray));
string[] arr = null;
string[] data = null;
if (c != null)
{
arr = c.Split('\n');
if (arr.Length > 0)
{
data = arr[0].Split(',');
}
}
for(int index = 0; index < 4; index++)
{
itemsControl.Items.Add(float.Parse(data[index]));
}
itemsControl.Clear();
If anyone has an idea of how I can do this I will be very grateful, thanks in advance and I will try to answer any questions as soon as possible!
What you're missing is an understanding of how bindings are triggered to update.
The INotifyPropertyChanged interface contains a method (PropertyChanged) and when called and passed the name of a property will tell the binding system that the property has changed and the binding should be updated.
INotifyCollectionChanged is the equivalent for collections, and communicates when a collection has changed. i.e. something added, removed, or the list cleared.
ObservableCollection<T> contains an implementation of INotifyCollectionChanged that makes it easy to work with lists, collections, etc. that change.
If you used an ObservableCollection<float> instead of an array you'd be able to modify the list and have the UI updated to reflect this easily.
As a starter, see the following which demonstrates how easy it is to use an ObservableCollection.
XAML:
<StackPanel>
<Button Click="Button_Click">add an item</Button>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
code behind;
public MainPage()
{
this.InitializeComponent();
// Initialize the property
this.Items = new ObservableCollection<string>();
// Use self as datacontext (but would normally use a separate viewmodel)
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// add a new item to the UI
this.Items.Add(DateTime.Now.ToString());
}
// The "collection" that is shown in the UI
public ObservableCollection<string> Items { get; set; }

ListView Keep ListViewHeaderItem On Top

I start by explaining what I want to achieve:
The Letter "A" is one ListViewHeaderItem in my Listview. Without Scrolling the top of the List is looking like this.
After I am Scrolling the ListViewHeaderItem "A" is moving downwards with the rest of the items -
but how can I achieve that the Header is staying on top as Kind of the first item until the Letter "B" with ist subitems is coming? An example of the behaviour I want to achieve is the official "Mail" app for Windows 10 by Microsoft. It is keeping the datetime at the top until emails are coming which have been written one day earlier.
I don't know if this question is already existing but I don't know how it is called and I don't know what to Google for.
According to your description, I think what you want is a grouped ListView. The key points here is using CollectionViewSource as ItemsSource and setting GroupStyle to specify how groups are displayed. Following is a simple sample:
In XAML
<Page.Resources>
<CollectionViewSource x:Name="groupInfoCVS" IsSourceGrouped="True" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{Binding Source={StaticResource groupInfoCVS}}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Margin="15" Text="{Binding Path=Text}" />
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Background="LightGray">
<TextBlock Margin="10" Foreground="Black" Text="{Binding Key}" />
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</Grid>
And in code-behind
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
List<TestDemo> list = new List<TestDemo>();
for (int i = 0; i < 6; i++)
{
list.Add(new TestDemo { Key = "A", Text = $"Test A {i}" });
list.Add(new TestDemo { Key = "B", Text = $"Test B {i}" });
}
var result = from t in list group t by t.Key;
groupInfoCVS.Source = result;
}
}
public class TestDemo
{
public string Key { get; set; }
public string Text { get; set; }
}
And it looks like:
For more info, please see How to group items in a list or grid (XAML) and Simple ListView Sample in ListView and GridView sample on GitHub.

Categories

Resources