Oxyplot graphs 13 points which are derived from the 6 user input text boxes. The values in the text boxes are held in public variables in the MainWindow.xaml.cs class. The variables are updated when the user presses enter in the text box. How would I make the refresh button refresh the graph.
private void RefreshButton_Click(object sender, RoutedEventArgs e)
{
//Refresh The Graph
}
I think that this would be done using the
PlotModel.RefreshPlot()
method, but I am not sure how to implement it because of Oxyplot's poor documentation.
I just updated to a new version of OxyPlot via NuGet. I'm using OxyPlot.Wpf v20014.1.277.1 and I think you now need to call InvalidatePlot(bool updateData) on the PlotModel instead of RefreshPlot (which is no longer available). I tested this in my sample code and it worked as expected.
If you want to refresh the plot and update the data collections, you need to pass true to the call:
PlotModel.InvalidatePlot(true)
Give x:Name to OxyPlot instance in XAML:
<oxy:Plot x:Name="Plot1"/>
and on button click handler, refresh like this:
private void RefreshButton_Click(object sender, RoutedEventArgs e)
{
Plot1.RefreshPlot(true);
}
The cleanest way I've found to get "sort of" auto-update is reacting to CollectionChanged on the collection that is LineSeries' ItemsSource.
In ViewModel:
ObservableCollection<DataPoint> Data { get; set; }
= new ObservableCollection<DataPoint>();
public PlotModel PlotModel
{
get { return _plot_model; }
set
{
_plot_model = value;
RaisePropertyChanged(() => PlotModel);
}
}
PlotModel _plot_model;
// Inside constructor:
Data.CollectionChanged += (a, b) => PlotModel.InvalidatePlot(true);
In the current OxyPlot.Wpf (1.0.0-unstable1983) you have two options:
Bind the Series.ItemsSource property from XAML to a collection in your viewmodel and exchange the whole collection, when you need an update. This also allows for concurrent async updates with larger data sets.
Bind the Plot.InvalidateFlag property of type int to your viewmodel and increment whenever you need an update. I haven't tested this approach, though.
The following code illustrates both options (pick one). XAML:
<oxy:Plot InvalidateFlag="{Binding InvalidateFlag}">
<oxy:Plot.Series>
<oxy:LineSeries ItemsSource="{Binding DataSeries}" />
</oxy:Plot.Series>
</oxy:Plot>
Updates on the ViewModel:
private async Task UpdateAsync()
{
// TODO do some heavy computation here
List<DataPoint> data = await ...
// option 1: Trigger INotifyPropertyChanged on the ItemsSource.
// Concurrent access is ok here.
this.DataSeries = data; // switch data sets
// option 2: Update the data in place and trigger via flag
// Only one update at a time.
this.DataSeries.Clear();
data.ForEach(this.DataSeries.Add);
this.InvalidateFlag++;
}
After having the same question with the same issue, it would seem that the only working solution (at least to my point of view) is as followed :
PlotView.InvalidatePlot(true)
Doing so, after updating one or multple Series do refresh your PlotView.
The refresh rate depends on how often, or at which rate your serie(s) is/are updated.
Here is a code snippet (on Xamarin Android but should work anyway) :
PlotView resultsChart = FindViewById<PlotView>(Resource.Id.resultsChart);
PlotModel plotModel = new PlotModel
{
// set here main properties such as the legend, the title, etc. example :
Title = "My Awesome Real-Time Updated Chart",
TitleHorizontalAlignment = TitleHorizontalAlignment.CenteredWithinPlotArea,
LegendTitle = "I am a Legend",
LegendOrientation = LegendOrientation.Horizontal,
LegendPlacement = LegendPlacement.Inside,
LegendPosition = LegendPosition.TopRight
// there are many other properties you can set here
}
// now let's define X and Y axis for the plot model
LinearAxis xAxis = new LinearAxis();
xAxis.Position = AxisPosition.Bottom;
xAxis.Title = "Time (hours)";
LinearAxis yAxis = new LinearAxis();
yAxis.Position = AxisPosition.Left;
yAxis.Title = "Values";
plotModel.Axes.Add(xAxis);
plotModel.Axes.Add(yAxis);
// Finally let's define a LineSerie
LineSeries lineSerie = new LineSeries
{
StrokeThickness = 2,
CanTrackerInterpolatePoints = false,
Title = "Value",
Smooth = false
};
plotModel.Series.Add(lineSerie);
resultsChart.Model = plotModel;
Now, whenever you need to add DataPoints to your LineSerie and to updated automatically the PlotView accordingly, just do as followed :
resultsChart.InvalidatePlot(true);
Doing so will automatically refresh your PlotView.
On a side note, the PlotView will also be updated when an event occurs such as a touch, a pinch to zoom, or any kind of UI-related events.
I hope I could help. I had trouble with this for a very long time.
Exists three alternatives how refresh plot (from OxyPlot documentation):
Change the Model property of the PlotView control
Call Invalidate on the PlotView control
Call Invalidate on the PlotModel
Another two years later... this solution works for me, because I have no oxyplot models and I´m missing some of the named functions from above.
code behind:
public partial class LineChart : UserControl
{
public LineChart()
{
InitializeComponent();
DataContext = this;
myChart.Title = "hier könnte Ihr Text stehen!";
this.Points = new List<DataPoint>();
randomPoints();
}
public IList<DataPoint> Points { get; private set; }
public void randomPoints()
{
Random rd = new Random();
String myText = "";
int anz = rd.Next(30, 60);
for (int i = 0; i < anz; i++)
myText += i + "," + rd.Next(0, 99) + ";";
myText = myText.Substring(0, myText.Length - 1);
String[] splitText = myText.Split(';');
for (int i = 0; i < splitText.Length; i++)
{
String[] tmp = splitText[i].Split(',');
Points.Add(new DataPoint(Double.Parse(tmp[0].Trim()), Double.Parse(tmp[1].Trim())));
}
while (Points.Count > anz)
Points.RemoveAt(0);
myChart.InvalidatePlot(true);
}
}
To update your data don't exchange the whole IList, rather add some new DataPoints to it and remove old ones at position 0.
XAML:
<UserControl x:Class="UxHMI.LineChart"
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"
xmlns:local="clr-namespace:UxHMI"
xmlns:oxy="http://oxyplot.org/wpf"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid x:Name="Container" Background="White">
<oxy:Plot x:Name="myChart" Title="{Binding Title}" FontFamily="Bosch Sans Medium" Foreground="#FF0C6596" FontSize="19" Canvas.Left="298" Canvas.Top="32" Background="AliceBlue" Margin="0,0,10,0">
<oxy:Plot.Series>
<oxy:LineSeries x:Name="ls" Background="White" ItemsSource="{Binding Points}" LineStyle="Solid" Color="ForestGreen" MarkerType="None" MarkerSize="5" MarkerFill="Black">
</oxy:LineSeries>
</oxy:Plot.Series>
</oxy:Plot>
<Button x:Name="button" Content="Random" HorizontalAlignment="Left" Margin="0,278,0,0" VerticalAlignment="Top" Width="75" Click="button_Click"/>
</Grid>
important are the x:Name="myChart" and ItemsSource="{Binding Points}"
I hope this is useful for someone out there
Related
I need to open a new window while passing a string into its constructor, and this seems to preclude some important data binding in the new window.
My MainWindow consists of a TextBox where the user puts in some text and then clicks a button, opening a new window which makes an OxyPlot displaying that text. This is important because this single user input will eventually be used to open many different windows and make different plots using the same text. Can't make the user provide the string every time they open a new window.
MainWindow with some user input
The new window ("FirstPlotWindow" because there will be more windows implemented later) uses OxyPlot to plot these words at random positions. The new window also has a second TextBox and a second button for adding more words to the plot.
FirstPlotWindow with user's words plotted
I've done my best to make it so that when the user types some words into this second box and clicks the second button, more words will be added to the plot. But it doesn't work.
Here is the XAML code for FirstPlotWindow:
<Window x:Class="WpfApp2.FirstPlotWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp2"
xmlns:oxy="http://oxyplot.org/wpf"
mc:Ignorable="d"
Title="FirstPlotWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="5*"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="userInput2" Margin="0,40,0,0" HorizontalAlignment="Center" VerticalAlignment="Top" Width="100" Height="100"/>
<Button x:Name="changePlotButton" Content="Put new words
into plot" HorizontalAlignment="Center" VerticalAlignment="Center" Click="changePlotButton_Click"/>
<oxy:PlotView x:Name="firstPlotView" Grid.Column="1" Model="{Binding MyModel}"/>
</Grid>
As you can see, I haven't set up the DataContext here because I need the plot model to take the original words ("Hello darkness my old friend") as an argument, as you can see below. Here is the code-behind for the window:
using System.Collections.Generic;
using System.Windows;
namespace WpfApp2
{
public partial class FirstPlotWindow : Window
{
private FirstPlotModel modelInWindow;
public FirstPlotModel ModelInWindow
{
get { return (FirstPlotModel)DataContext; }
set { modelInWindow = value; }
}
public FirstPlotWindow(string inputText)
{
InitializeComponent();
ModelInWindow = new FirstPlotModel(inputText);
DataContext = modelInWindow;
}
private void changePlotButton_Click(object sender, RoutedEventArgs e)
{
List<string> moreWords = new List<string>(userInput2.Text.Split(" "));
ModelInWindow.PopulatePlotWithWords(moreWords);
}
}
}
And here is the code for FirstPlotModel.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using OxyPlot;
using OxyPlot.Annotations;
using OxyPlot.Axes;
namespace WpfApp2
{
public class FirstPlotModel: INotifyPropertyChanged
{
public PlotModel MyModel { get; set; }
Random randomizer;
public event PropertyChangedEventHandler PropertyChanged;
public FirstPlotModel(string inputText)
{
MyModel = new PlotModel { Title = "First Plot" };
MyModel.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom });
MyModel.Axes.Add(new LinearAxis { Position = AxisPosition.Left });
List<string> wordList = new List<string>(inputText.Split(" "));
PopulatePlotWithWords(wordList);
}
public void PopulatePlotWithWords(List<string> wordList)
{
randomizer = new Random();
foreach (string word in wordList)
{
MyModel.Annotations.Add(new TextAnnotation
{
TextPosition = new DataPoint(
randomizer.Next(1, 100),
randomizer.Next(0, 100)),
Text = word
});
}
}
private void OnPropertyChanged([CallerMemberName] String propName = null)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
}
I know that entering the second set of words and pressing the second button successfully changes the MyModel property in the ModelInWindow object, because when I put a breakpoint after changePlotButton_Click I can see that the words have been added. Since the MyModel property changed, I believe the OxyPlot should update. But the OxyPlot doesn't change.
I designed the code to be as close to this as possible: OxyPlot WPF not working with Button Click
But declaring the DataContext in XAML seems impossible here! Please help!!
I can't reproduce your code so it is hard to tell, but I can't see that you update the plot. This has to be done manually with
MyModel.InvalidatePlot(true);
OxyPlot doesn't support binding like this. Implementing INotifyPropertyChanged and OnPropertyChanged will not make an OxyPlot automatically update. Instead, you must declare a new DataContext every time you click; do this in the C# code, whether or not the binding is initially set up in XAML.
The typical way of setting the Series Fill and Stroke are explained perfectly on the Livecharts website. However, in order to set custom labels for points you need to create the Series in the view model (Shown below). This prevents you from being able to call Fill or Stroke in the XAML as you don't have each series being created like the example below.
<lvc:CartesianChart Name="Chart" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Margin="15">
<lvc:CartesianChart.Series>
<lvc:LineSeries Values="{Binding Values}" LineSmoothness="1" StrokeThickness="10"
DataLabels="True" FontSize="20" Foreground="#6B303030"
Stroke="White" Fill="Transparent" PointGeometrySize="0"/>
</lvc:CartesianChart.Series>
My current code which creates the series and its associated labels.
ViewModel
ABValuesSC = new SeriesCollection
{
new LineSeries
{ Values = ABValues,
DataLabels = true,
FontSize = 14,
//MinPointShapeDiameter = 15,
StrokeDashArray = new System.Windows.Media.DoubleCollection {2},
Fill = System.Windows.Media.Brushes.Transparent,
LabelPoint = point =>
{if(point.Key==0)
{
return "A";
}
else
{
return "B";
}
}
},
new ScatterSeries
{ Values = TriggerValues,
DataLabels = true,
FontSize = 14,
MinPointShapeDiameter = 15,
LabelPoint = point =>
{if(point.Key==0)
{
return "1";
}
else
{
return "2";
}
}
},
new LineSeries
{ Values = NAVmatValues,
LineSmoothness=0,
}
};
XAML
<lvc:CartesianChart Series="{Binding ABValuesSC}"/>
Giving you this output.
Is there a method for accessing a series fill for the chart to change it from the default and have it be bindable? for example would it be possible to have the colours be bound to a list
or is there a better way of making the labels for my chart such that i can use a similar method to the example at the top of this post?
Instead of creating a SeriesCollection programmatically and bind it to the View, its possible to define (most) of these Things directly in the XAML and only bind the Things you need to change in your ViewModel.
Move to XAML
As far as I understood your code, you only want to change the Values and the Fill in your ViewModel, so we put your "configuration" in the XAML which looks something like this:
<lvc:CartesianChart>
<lvc:CartesianChart.Series>
<lvc:LineSeries Values="{Binding ABValues}" DataLabels="True" FontSize="14" StrokeDashArray="1,1" Fill="{Binding ABColor}" LabelPoint="{Binding ABLabelPoint}"/>
<lvc:ScatterSeries Values="{Binding TriggerValues}" DataLabels="True" FontSize="14" MinPointShapeDiameter="15" LabelPoint="{Binding TriggerLabelPoint}"/>
<lvc:LineSeries Values="{Binding NAVmatValues}" LineSmoothness="0"/>
</lvc:CartesianChart.Series>
</lvc:CartesianChart>
LabelPoint Binding
The LabelPoint cant be set (or at least i dont know how) in the XAML and must be provided as a Property in your ViewModel (see Code below)
class YourClass
{
//Property to Bind
public Func<ChartPoint,string> ABLabelPoint { get; set; }
//Constructor
public YourClass()
{
//Define LabelPoint, where 0 = A, 1 = B etc.
//Or use your Code, doesent really matter
ABLabelPoint = point => ((char)(point.X + 65)).ToString();
}
}
(Dont forget to do this for the Scatterseries LabelPoint as TribberLabelPoint Property)
Values Binding
The Values are now bound, therefore you must expose them as a Property like this
public ChartValues<ValueType> ABValues { get; set; }
Note: Replace ValueType with the used Type, eg. int or byte.
Fill Color Binding
Like the Values, the Fill color is bound to a Property which must be implemented. Make sure you notify the View when the Color Changes (see INotifyPropertyChanged)
If your class already has this interface implemented it could look like this
//private Field
private SolidColorBrush _abColor = new SolidColorBrush(Colors.Green);
//Public Property which the XAML binds to
public SolidColorBrush ABColor
{
get { return _abColor; }
set
{
_abColor = value;
OnPropertyChanged();
}
}
As you already use MVVM, use a command to manipulate color. In the Command delegate all you have to do is to access the series collection and pick out the series you want to change. Note that you have to cast it to the right series type.
((LineSeries)ABValuesSC [0]).Fill = Brushes.Aqua; //change fill of first series
This way you can manipulate any property of the series you want, not just fill.
I have been trying to incorporate pie charts using LVC, and it works great. I've been playing around with this simple code... .xml....
<UserControl x:Class="UI.PieChart"
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"
xmlns:local="clr-namespace:UI"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="500"
d:DataContext = "{d:DesignInstance local:PieChart}">
<Grid>
<lvc:PieChart LegendLocation="Bottom" DataClick="Chart_OnDataClick" Hoverable="False" DataTooltip="{x:Null}">
<lvc:PieChart.Series>
<lvc:PieSeries Name ="Portion" Title="Maria" Values="3" DataLabels="True"
LabelPoint="{Binding PointLabel0}"/>
<lvc:PieSeries Title="Charles" Values="4" DataLabels="True"
LabelPoint="{Binding PointLabel1}"/>
<lvc:PieSeries Title="Frida" Values="6" DataLabels="True"
LabelPoint="{Binding PointLabel2}"/>
<lvc:PieSeries Title="Frederic" Values="2" DataLabels="True"
LabelPoint="{Binding PointLabel3}"/>
</lvc:PieChart.Series>
</lvc:PieChart>
</Grid>
</UserControl>
and this code which activates the user actions... .xaml.cs
namespace UI
{
/// <summary>
/// Interaction logic for DataChart.xaml
/// </summary>
public partial class PieChart : UserControl
{
public PieChart()
{
InitializeComponent();
PieSeries()
PointLabel = chartPoint =>
string.Format("{0} ({1:P})", chartPoint.Y, chartPoint.Participation);
DataContext = this;
}
public Func<ChartPoint, String> PointLabel { get; set;}
private void Chart_OnDataClick(object sender, ChartPoint chartpoint)
{
var chart = (LiveCharts.Wpf.PieChart) chartpoint.ChartView;
foreach (PieSeries series in chart.Series)
series.PushOut = 0;
var selectedSeries = (PieSeries)chartpoint.SeriesView;
selectedSeries.PushOut = 8;
}
}
}
I am totally new to C#, .xaml, WPF, and LVC... But what I would like to do is not hardcode the amount of wedges in the PIE chart. Instead, I'd like to create a pie chart based on the data I'm given. I'd like to do this in C#. Where when I instantiate the class PieChart(). I can pass 5 in the parameter like so, PieChart(5). Then that will create the PieChart, and then continue to create 5 PieSeries or 5 wedges... side question, are there better tools to this then LVC or even WPF?
One approach would be to create a SeriesCollection, add a list of PieSeries to this based on some user input, and bind your PieChart in XAML to the SeriesCollection. You'll need to implement INotifyPropertyChanged as well to ensure that changes to the SeriesCollection are reflected in the user interface (here implemented in RaisePropertyChanged):
The SeriesCollection that you'll bind to:
private SeriesCollection myPie = new SeriesCollection();
public SeriesCollection MyPie
{
get
{
return myPie;
}
set
{
myPie = value;
RaisePropertyChanged("MyPie");
}
}
Code to process some user input (here based on class UserInput with property Value). The number of slices will equal the number of PieSeries you add to the SeriesCollection:
foreach(item in UserInput)
{
MyPie.Add(new PieSeries { Values = new ChartValues<int> { item.Value }, DataLabels = true };
}
In your XAML, bind the SeriesCollection to a PieChart:
<lvc:PieChart Series="{Binding MyPie}"/>
I am starting to play with Realm, and I am trying to bind a collection from the Realm database to a ListView. The binding works fine, but my ListView does not update when adding new items. My understanding is that IRealmCollection<> implements INotifyCollectionChanged and INotifyPropertyChanged events.
Here is a simple application to reproduce the issue:
View:
<Page x:Class="App3.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:App3"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<Button Click="ButtonBase_OnClick" Content="Add" />
<ListView x:Name="ListView">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Id}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
</Page>
CodeBehind:
namespace App3
{
public class Thing : RealmObject
{
public string Id { get; set; } = Guid.NewGuid().ToString();
}
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
private Realm _realm;
private IRealmCollection<Thing> things;
public MainPage()
{
this.InitializeComponent();
_realm = Realm.GetInstance();
things = (IRealmCollection<Thing>)_realm.All<Thing>();
ListView.ItemsSource = things;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
_realm.Write(() =>
{
var thing = new Thing();
_realm.Add(thing);
});
}
}
}
I normally use MVVM (Template10), but this is a simple application to demonstrate the issue. Clicking the Add button adds an item to the database, but the ListView only updates when the application is first loaded. I have read similar questions, but I have not been able to find an answer that works yet. Inverse Relationships and UI-Update not working? is the closest I have found yet, but still does not fix the issue.
EDIT
I can force it to rebind like so:
ListView.ItemsSource = null;
ListView.ItemsSource = things;
But that is not optimal. I am trying to take advantage of Realm's "live objects" where the collection should always know when items are changed or added.
EDIT 2
Setting BindingMode=OneWay in code-behind also does not change the behavior:
_realm = Realm.GetInstance();
things = (IRealmCollection<Thing>)_realm.All<Thing>();
var binding = new Binding
{
Source = things,
Mode = BindingMode.OneWay
};
ListView.SetBinding(ListView.ItemsSourceProperty, binding);
SOLUTION
It turned out to be a known issue in IRealmCollection: https://github.com/realm/realm-dotnet/issues/1461#issuecomment-312489046 which is fixed in Realm 1.6.0. I have updated to the pre-release NuGet package and can confirm that the ListView now updates as expected.
Set Mode=OneWay in Binding
Method 1: In Xaml
<ListView ItemsSource="{x:Bind things, Mode=OneWay}" />
Method 2: In Code Behind
Binding myBind = new Binding();
myBind.Source = things;
myBind.Mode = BindingMode.OneWay;
myListView.SetBinding(ListView.ItemsSourceProperty, myBind);
It is a bug in IRealmCollection. You can use Prerelease Nuget to solve it.
For more info:
IRealmCollection does not update UWP ListView
GitHub Issue: IRealmCollection does not update UWP ListView
As I was required to sort of mask input in a textbox, I decided to construct my own control to handle this.
One of many templates could be "Size {enter size} Colour {enter colour}" which I've broken down to create a series of controls. The custom control that extends StackPanel which I've named CustomTextBox generates the following from the constructor.
// Pseudo
Children = {
Label = { Content = "Size" },
TextBox = { Text = "enter size" },
Label = { Content = "Colour" },
TextBox = { Text = "enter colour" }
// .. and an arbitrary amount of more Labels and TextBoxes in no particular order
}
So far so good. But when I want it to render.. That's where my headache starts.
I've tried to add the controls to the Children property and Measure/Arrange on the parent, itself and all the Children. ActualHeight and ActualWidth do change to something other than 0, but they won't render/display/become visible whatsoever.
I've also tried to use an ItemsControl and add the controls to the ItemsSource property to no avail.
I've tried to predefine sizes on everything, colour the background red and all, but the elusive controls remain to be caught and tied to my screen.
There's got to be a huge "Oooh..." here that I just can't find. I refuse to believe that this can't be done. I mean, it's WPF. WPF is awesome.
Edit Updated to what I currently have that seems most likely to work - still doesn't though.
Whatever I do in the designer shows up, but nothing I do in the CustomTextBox makes any visible difference.
Edit
New headline that fits the problem better.
Also, I've found several examples of programmatically adding controls. Take this article for example. I fail to see the difference between my scenario and theirs, except that theirs work and the buttons are visible.
Update3
The mistake was to assume, that one can simply replace control in visual tree by assigning in codebehind a new control to it's name (specified in xaml)
Updated2
Your mistake was following. If you write
<TextBlock Name="tb" Text="tb"/>
and then in code you will do
tb = new TextBlock() { Text = "Test" };
then you will have a new textblock as a variable, and nothing in xaml will change. You either have to change existing control, or remove old control and add new.
I'm talking about your Headline, Subtext & Description. You don't change them
Updated:
Here is an example of dynamically creating controls by specifying input mask:
MainWindow.xaml
<Window x:Class="WpfApplication35.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" xmlns:local="clr-namespace:WpfApplication35">
<Grid>
<local:UserControl1 x:Name="myUserControl"/>
</Grid>
</Window>
MainWindow.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
myUserControl.BuildControls("a {enter a} b {enter b1}{enter c2}");
}
}
UserControl1.xaml
<UserControl x:Class="WpfApplication35.UserControl1"
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"
d:DesignHeight="30" d:DesignWidth="300">
<WrapPanel Name="root" Orientation="Horizontal"/>
</UserControl>
UserControl1.cs
public partial class UserControl1 : UserControl
{
public List<CustomField> Fields = new List<CustomField>();
public UserControl1()
{
InitializeComponent();
}
public UserControl1(string mask)
{
InitializeComponent();
BuildControls(mask);
}
public void BuildControls(string mask)
{
//Parsing Input
var fields = Regex.Split(mask, #"(.*?\}\s)");
foreach (var item in fields)
{
if (item != "")
{
int index = item.IndexOf('{');
string namestring = item.Substring(0, index).Trim();
var field = new CustomField() { Name = namestring };
string valuesstring = item.Substring(index, item.Length - index).Trim();
var values = valuesstring.Split(new char[] { '{', '}' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var val in values)
{
var valuewrapper = new FieldValue() { Value = val };
field.Values.Add(valuewrapper);
}
Fields.Add(field);
}
}
foreach (var field in Fields)
{
var stackPanel = new StackPanel() { Orientation = Orientation.Horizontal };
var label = new Label() { Content = field.Name, Margin = new Thickness(4) };
stackPanel.Children.Add(label);
foreach (var item in field.Values)
{
var tb = new TextBox() { Margin = new Thickness(4), Width = 200 };
tb.SetBinding(TextBox.TextProperty, new Binding() { Path = new PropertyPath("Value"), Source = item, Mode = BindingMode.TwoWay });
stackPanel.Children.Add(tb);
}
root.Children.Add(stackPanel);
}
}
}
public class CustomField
{
public string Name { get; set; }
public List<FieldValue> Values = new List<FieldValue>();
}
public class FieldValue
{
public string Value { get; set; }
}
This way fields and values are gonna be represented by Fields collection in UserControl1. Values of fields are updated as user types something. But only one-way, i.e. user input updates corresponding Value property, but changing Value property at runtime will not affect corresponding textbox. To implement updating from Value to textbox you have to implement INotifyProperty interface
Obsolete
Since you've asked.
There are hundreds of possible implementations, depending on what are you trying to archieve, how do you want validation to be, do you want to use MVVM, do you want to use bindings etc. There are generally 2 approaches : creating usercontrol and creating custom control. First one suits you better I believe.
Create a usercontrol with following xaml:
<Grid Height="24">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="Size: " Grid.Column="0"/>
<TextBox Name="tbSize" Grid.Column="1"/>
<Label Content="Colour:" Grid.Column="2"/>
<TextBox Name="tbColour" Grid.Column="3"/>
</Grid>
In code-behind you can access TextBoxes by their name and do whatever you want to do.
You can use usercontrol in both xaml and codebehind.
In xaml:
Specify alias for namespace of your usercontrol (look at xmlns:local)
<Window x:Class="WpfApplication35.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"
xmlns:local="clr-namespace:WpfApplication35">
<Grid>
<local:UserControl1/>
</Grid>
</Window>
In codebehind you can use it like this:
public MainWindow()
{
InitializeComponent();
var myUserControl = new UserControl1();
}
There is a lot to say and these are basic things, so check tutorials and ask questions.
P.S. If you are learning WPF it's mandatory to learn bindings.