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)
Related
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.
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; }
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)
I cannot find any examples to make me understand how and if I can change the databind in c# at the click of a button on, in my case a toggleswitch, Basically I have 32 buttons in my app and those 32 buttons act the same but need different text with-in them depending on some toggle switches they are currently databinded so the text can be saved and retrieved from local storage but what values it gets depends on the state of these toggle switches.
So I currently have :
<Button x:Name="_ovButton1" Content="{Binding Source={StaticResource AppSettings}, Path=ovName1_1Value, Mode=TwoWay}" Margin="2,0,250,0" VerticalAlignment="Top" FontSize="14" Height="72" FontWeight="Bold" MouseLeftButtonUp="_ovButton1_MouseLeftButtonUp" MouseLeftButtonDown="_ovButton1_MouseLeftButtonDown" ClickMode="Hover" Hold="_ovButton1_Hold"/>
and I want when a user changes the state of a toggleswitch to change the
{StaticResource AppSettings}, Path=ovName1_1Value, Mode=TwoWay}
to for example:
{StaticResource AppSettings}, Path=ovName1_2Value, Mode=TwoWay}
but I cannot find any example that shows how to do that in c#
what code do I need to do that?
You can specify the target of databinding in code like this:
MyData myDataObject = new MyData(DateTime.Now);
Binding myBinding = new Binding("MyDataProperty");
myBinding.Source = myDataObject;
myText.SetBinding(TextBlock.TextProperty, myBinding);
See more at http://msdn.microsoft.com/en-us/library/ms742863.aspx
-- Edit Note I don't have access to a WP8 Emulator to test this ---
In the view model it looks like this:
public List<string> Members
{
get { return _Members; }
set { _Members = value; OnPropertyChanged(); }
}
public MainVM()
{
// Simulate Asychronous access, such as to a db.
Task.Run(() =>
{
Thread.Sleep(2000);
Members = new List<string>() {"Alpha", "Beta", "Gamma", "Omega"};
});
}
The code behind on the main page sets the datacontext (shared with all the child controls) as such:
public MainWindow()
{
InitializeComponent();
// Set the windows data context so all controls can have it.
DataContext = new MainVM();
}
The Mainpage Xaml to bind to members is like this
<Button Height="30"
Width="80"
Margin="10"
DataContext="{Binding Members}"
Content="{Binding Path=[0] }" />
<Button Height="30"
Width="80"
Margin="10"
DataContext="{Binding Members}"
Content="{Binding Path=[1] }" />
<Button Height="30"
Width="80"
Margin="10"
DataContext="{Binding Members}"
Content="{Binding Path=[2] }" />
<Button Height="30"
Width="80"
Margin="10"
DataContext="{Binding Members}"
Content="{Binding Path=[3] }" />
The result is this visually:
I based this on my blog article Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding for more info and a fuller example.
I think your best bet is going to be to use a collection of strings and bind to that collection. You can either change the collection when a toggle is switched, or keep 6 collections and bind to the collection that is for the toggle.
Xaml:
<ItemsControl x:Name="Buttons" ItemsSource="{Binding ButtonTextCollection}">
<ItemsControl.ItemsPanel>
<toolkit:WrapPanel/>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="100" Height="70" Content="{Binding}" Click="OnButtonClick"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Your code-behind would have the event handler for your button click
private void OnButtonClick(object sender, RoutedEventArgs e)
{
var text = ((Button) sender).Content.ToString();
// Send the text
}
Your ViewModel would hold the ButtonTextCollection property and would change based on the toggle.
public ICollection<string> ButtonTextCollection
{
get { return _buttonTextCollection; }
set
{
_buttonTextCollection = value;
OnPropertyChanged("ButtonTextCollection");
}
}
When you want to change the text, you would change the ButtonTextCollection
public void ChangeButtonText()
{
ButtonTextCollection = new Collection<string> {"A", "B",...};
}
I am attempting to create a program in which the User can create multiple profiles. These profiles can be accessed via buttons that appear as each profile is completed.
My problem:
I have no clue how to make the created buttons persist after the program is exited(I need to save the buttons?)
Visually, this is program's process: 1) Enter your information, click continue 2) View a display page of what you entered, click done. 3) This adds a button to the final window, the button of course takes you to 4) Your profile you just created.
After this, the program ends and nothing is saved. I'm fairly new to c# and am quite confused on how to "save" multiple buttons without massively complicating the code. I'm a complete noob to c# and have a little Java experience. Am I going about this correctly? I'm pretty sure its possible but have no idea to go about it.
I will include my code below. I'm working in visual studios 2012. any help would be appreciated!
MainWindow XAML:
<Window x:Class="VendorMain.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Label Content="FirstName" HorizontalAlignment="Left" Margin="63,45,0,0" VerticalAlignment="Top"/>
<Label Content="LastName" HorizontalAlignment="Left" Margin="63,71,0,0" VerticalAlignment="Top"/>
<Label Content="Image" HorizontalAlignment="Left" Margin="63,102,0,0" VerticalAlignment="Top"/>
<Image Name="imgPhoto" Stretch="Fill" Margin="63,133,303,69"></Image>
<Button Name="UploadImageButton" Content="Upload Image" HorizontalAlignment="Left" Margin="130,105,0,0" VerticalAlignment="Top" Width="84" Click="UploadImageButton_Click"/>
<TextBox Name="AssignFirstName" Text="{Binding SettingFirstname}" HorizontalAlignment="Left" Height="23" Margin="130,48,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" />
<TextBox Name="AssignLastName" Text="{Binding SettingLastName}" HorizontalAlignment="Left" Height="23" Margin="130,75,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<Button Name="ContinueToDisplayWindow" Content="Continue" HorizontalAlignment="Left" Margin="409,288,0,0" VerticalAlignment="Top" Width="75" Click="ContinueToDisplayWindow_Click" />
</Grid>
MainWindow Code:
namespace VendorMain
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void UploadImageButton_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog op = new OpenFileDialog();
op.Title = "Select a picture";
op.Filter = "All supported graphics|*.jpg;*.jpeg;*.png|" +
"JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|" +
"Portable Network Graphic (*.png)|*.png";
if (op.ShowDialog() == true)
{
imgPhoto.Source = new BitmapImage(new System.Uri(op.FileName));
//SettingImage.Source = imgPhoto.Source;
}
}
private void ContinueToDisplayWindow_Click(object sender, RoutedEventArgs e)
{
DisplayPage displaypg = new DisplayPage();
displaypg.DpFirstName.Content = AssignFirstName.Text;
displaypg.DpLastName.Content = AssignLastName.Text;
displaypg.DpImage.Source = imgPhoto.Source;
displaypg.Show();
}
}
}
DisplayPage XAML:
<Window x:Class="VendorMain.DisplayPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DisplayPage" Height="300" Width="525">
<Grid>
<Label Name="DpFirstName" Content="{Binding getFirstNamePermenent}" HorizontalAlignment="Left" Margin="86,55,0,0" VerticalAlignment="Top"/>
<Label Name="DpLastName" Content="{Binding getLastNamePermenent}" HorizontalAlignment="Left" Margin="87,80,0,0" VerticalAlignment="Top"/>
<Image Name="DpImage" HorizontalAlignment="Left" Height="100" Margin="94,111,0,0" VerticalAlignment="Top" Width="100"/>
<Button Name="ButtonizeThisProfile_Button" Content="Done" HorizontalAlignment="Left" Margin="420,238,0,0" VerticalAlignment="Top" Width="75" Click="ButtonizeThisProfile_Button_Click"/>
</Grid>
DisplayPage Code:
namespace VendorMain
{
/// <summary>
/// Interaction logic for DisplayPage.xaml
/// </summary>
public partial class DisplayPage : Window
{
public Button bot1;
public DisplayPage()
{
InitializeComponent();
}
private void newBtn_Click(object sender, RoutedEventArgs e)
{
carryToFinalView();
}
private void ButtonizeThisProfile_Button_Click(object sender, RoutedEventArgs e)
{
UserProfiles uPro = new UserProfiles();
System.Windows.Controls.Button newBtn = new Button();
newBtn.Content = "Person1";
newBtn.Name = "NewProfileButtonAccess";
newBtn.Click += new RoutedEventHandler(newBtn_Click);
uPro.ButtonArea.Children.Add(newBtn);
uPro.Show();
}
public void carryToFinalView()
{
DisplayPage displaypg = new DisplayPage();
displaypg.DpFirstName.Content = DpFirstName.Content;
displaypg.DpLastName.Content = DpLastName.Content;
displaypg.DpImage.Source = DpImage.Source;
displaypg.Show();
}
}
}
UserProfile XAML:
<Window x:Class="VendorMain.UserProfiles"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="UserProfiles" Height="300" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".8*" />
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="6*"/>
<RowDefinition Height="11*"/>
</Grid.RowDefinitions>
<Label Content="User Profiles: " HorizontalAlignment="Left" Margin="37,47,0,0" VerticalAlignment="Top"/>
<StackPanel Name="ButtonArea" Grid.Column="2" Grid.Row="2">
</StackPanel>
<Button Name="AddAnotherProfileButton" Content="Button" HorizontalAlignment="Left" Margin="35,146,0,0" Grid.Row="1" VerticalAlignment="Top" Width="75" Click="AddAnotherProfileButton_Click"/>
</Grid>
UserProfile Code:
namespace VendorMain
{
public partial class UserProfiles : Window
{
public UserProfiles()
{
InitializeComponent();
}
private void AddAnotherProfileButton_Click(object sender, RoutedEventArgs e)
{
MainWindow mw = new MainWindow();
mw.Show();
}
}
}
As a self proclaimed 'noob', I fear that you won't receive an answer here. I certainly don't have time to repeatedly come back to answer a whole continuing stream of related questions. I also don't have time to provide you with a complete solution. However, I am happy to provide you with sort of 'pseudo code' to at least point you in the right direction... you will have to do a lot of this yourself.
So first things first, as mentioned in a comment, although it is possible, we don't generally save the UI Button objects, but instead we save the data that relates to the user profiles. Therefore, if you haven't done this already, create a User class that has all of the relevant properties. Implement the INotifyPropertyChanged Interface in it and add the SerializableAttribute to the class definition... this will enable you to save this class type as binary data.
Next, in your UI, don't add each Button in xaml... there's a better way. One way or another, add a collection property of type User or whatever your class is called, and set this as the ItemsSource of a ListBox. The idea here is to add a DataTemplate for your User type which will display each of the User items in the collection as a Button:
<DataTemplate x:Key="UserButtonTemplate" DataType="{x:Type DataTypes:User}">
<Button Text="{Binding Name}" Width="75" Click="AddAnotherProfileButton_Click" />
</DataTemplate>
You can find out more about DataTemplates in the Data Templates article.
Implementing this collection allows you to have and display any number of user profiles in your UI, rather than being restricted by screen size as your original example would be.
Now finally, on to saving the data... this can be achieved relatively simply using the following code:
try
{
using (Stream stream = File.Open("ProfileData.bin", FileMode.Create))
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter .Serialize(stream, usersList);
}
}
catch { }
One thing to note is that WPF wants us to use the ObservableCollection<T> class when displaying data in the UI, but this class causes problems when serializing data with this method... therefore, you will need to convert your ObservableCollection<T> to a List<T> or similar. However, this can be easily achieved:
List<User> usersList = users.ToList();
You can find out how to de-serialize your data from the C# Serialize List tutorial. You would deserialize (or load the data from the saved file) each time your application starts and re-save the file each time the program closes. You can add an event handler to the Application.Deactivated Event or the Window.Closing which gets called when the application closes, so you can put your code to save the file in there.
Well, I took longer and wrote more than I had expected, so I hope that helps.