Programmatically created controls not rendering - c#

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.

Related

Data binding fails when window constructor takes string as parameter (OxyPlot, WPF)

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.

Having n wedges in a Pie Chart or other charts using WPF and LVC(LiveCharts)

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}"/>

DependencyProperty binding error, programmatically

What I have is a well-working C# and XAML code, which does exactly what it is supposed to do, well, almost exactly. I am trying to make my custom, working, DependencyProperty for UserControl - and it is made, well-formed and supposedly working. There are two properties: SumOfApproximationsProperty and SumOfPositionsProperty. These getters and setters simply do not get invoked on certain actions - and this is my problem. They are declared in this UserControl class:
public partial class PresentationCell : UserControl
{
public Label SumOfApproximations;
public Label SumOfPositions;
public PresentationCell()
{
InitializeComponent();
DataContext = this;
this.MinHeight = 40;
this.MinWidth = 40;
SumOfApproximations = this.SumOfApproximation;
SumOfPositions = this.SumOfPosition;
}
public static readonly DependencyProperty SumOfApproximationsProperty =
DependencyProperty.Register("AproximationsProperty", typeof(String),
typeof(PresentationCell), new UIPropertyMetadata(null));
public static readonly DependencyProperty SumOfPositionsProperty =
DependencyProperty.Register("PositionsProperty", typeof(String),
typeof(PresentationCell), new UIPropertyMetadata(null));
public String AproximationsProperty
{
get { return (String)GetValue(SumOfApproximationsProperty); }
set { SetValue(SumOfApproximationsProperty, value); }
}
public String PositionsProperty
{
get { return (String)GetValue(SumOfPositionsProperty); }
set { SetValue(SumOfPositionsProperty, value); }
}
}
As You can see, it is composed of two Labels, that have their own text-setting properties. And here's this UserControl XAML:
// USER CONTROL XAML
<UserControl x:Class="PodstawyModelowaniaISymulacjiRozmytej.Controls.PresentationCell"
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="300" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*">
</ColumnDefinition>
<ColumnDefinition Width="2*">
</ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="1*">
</RowDefinition>
<RowDefinition Height="2*">
</RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Label Name="SumOfApproximation" Content="{Binding Path=AproximationsProperty}">
</Label>
</Grid>
<Grid Grid.Row="1">
</Grid>
</Grid>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="1*">
</RowDefinition>
<RowDefinition Height="2*">
</RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
</Grid>
<Grid Grid.Row="1">
<Label Name="SumOfPosition"></Label>
</Grid>
</Grid>
</Grid>
</UserControl>
This UserControl is going to be used with DataGrid (as it's cells), which is declared below (in XAML):
// MAIN WINDOW DATAGRIG DECLARATION MAINWINDOW.XAML
<Grid Grid.Row="2" Name="DataThree_Grid">
<DataGrid Name="ResultData_DataGrid" HeadersVisibility="Row" Margin="5 5 5 5"></DataGrid>
</Grid>
Here's the code, that prepares and creates a column in this DataGrid, filled with PresentationCell UserControls:
// MAIN WINDOW CREATE COLUMN FOR DATAGRID FUNCTION MAINWINDOW.XAML.CS
private DataGridTemplateColumn CreatePresentationTemplateColumn(Binding positions, Binding aproximations)
{
DataGridTemplateColumn doubleOnlyTextBoxColumn = new DataGridTemplateColumn();
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(PresentationCell));
DataTemplate dataTemplate = new DataTemplate();
factory.SetValue(PresentationCell.SumOfApproximationsProperty, aproximations);
factory.SetValue(PresentationCell.SumOfPositionsProperty, positions);
dataTemplate.VisualTree = factory;
doubleOnlyTextBoxColumn.CellTemplate = dataTemplate;
return doubleOnlyTextBoxColumn;
}
Other code, that can be deemed useful for You to answer this question:
// MAIN WINDOW INITIALIZING BUTTON MAINWINDOW.XAML.CS
private void SubtractionLR_Button_Click(object sender, RoutedEventArgs e)
{
MyData[] table = new MyData[]
{
new MyData
{
Values = new element[2]
{
new element
{
var1 = 7,
var2 = 6
},
new element
{
var1 = 4,
var2 = 1
}
}
},
new MyData
{
Values = new element[2]
{
new element
{
var1 = 67,
var2 = 3
},
new element
{
var1 = 44,
var2 = 1
}
}
}
};
fillPresentationDataGrid(ResultData_DataGrid, table);
}
Now, after all of the code has been described, the problem lingers here. As You can see, I am trying to create Binding object for my column of PresentationCell UserControls. The problem is, that this String in this Binding is rather unknown for me - its specification and so on. As a result, program cannot find data that should be provided to my control (and for its labels) through this binding. The data should come from MyData[] table. Program shows an error about "cannot find Values" etc. and the cells in DataGrid are blank.
// MAIN WINDOW FILLING PRESENTATION GRID FUNCTION MAINWINDOW.XAML.CS
private void fillPresentationDataGrid(DataGrid dataGrid, MyData[] table)
{
dataGrid.AutoGenerateColumns = false;
for (int i = 0; i < table[0].Values.Length; i++)
{
DataGridTemplateColumn col = CreatePresentationTemplateColumn(new Binding("Values[" + i + "].var1"), new Binding("Values[" + i + "].var2"));
dataGrid.Columns.Add(col);
}
dataGrid.ItemsSource = table;
}
EDIT
All I want is to get that MyData[] table content displayed on DataGrid control using my own custom UserControl. When I change that factory.SetValue(PresentationCell.SumOfApproximationsProperty, aproximations); into factory.SetValue(PresentationCell.SumOfApproximationsProperty, "foo");, the DataGrid will display "foo"'s.
EDIT2
Unfortunately, the problem still exists.
In the constructor of PresentationCell you set this.DataContext = this.
By setting DataContext to your control you are breaking the inheritance of this property and thats why setting the bindings in CreatePresentationTemplateColumn wont work.
To fix that you can remove this line and bind the controls by RelativeSource/ElementName or you can set the dataContext to the main grid in PresentationCell instead of the root level

How to refresh oxyplot plot when data changes

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

Allowing named elements in a multi-content custom control

Requirement
Let's start with what I am trying to achieve. I want to have a grid with 2 columns and a grid splitter (there is a little more to it that that, but let's keep it simple). I want to be able to use this grid in a lot of different places, so instead of creating it each time I want to make a custom control that contain two ContentPresenters.
The end goal is effectively to be able to write XAML like this:
<MyControls:MyGrid>
<MyControls:MyGrid.Left>
<Label x:Name="MyLabel">Something unimportant</Label>
</MyControls:MyGrid.Left>
<MyControls:MyGrid.Right>
<Label>Whatever</Label>
</MyControls:MyGrid.Right>
</MyControls:MyGrid>
IMPORTANT: Notice that I want to apply a Name to my Label element.
Attempt 1
I did a lot of searching for solutions, and the best way I found was to create a UserControl along with a XAML file that defined my grid. This XAML file contained the 2 ContentPresenter elements, and with the magic of binding I was able to get something working which was great. However, the problem with that approach is not being able to Name the nested controls, which results in the following build error:
Cannot set Name attribute value 'MyName' on element 'MyGrid'. 'MyGrid'
is under the scope of element 'MyControls', which already had a name
registered when it was defined in another scope.
With that error in hand, I went back to Dr. Google...
Attempt 2 (current)
After a lot more searching I found some information here on SO that suggested the problem was due to having an associated XAML file with the MyGrid class, and the problem should be solvable by removing the XAML and creating all the controls via code in the OnInitialized method.
So I headed off down that path and got it all coded and compiling. The good news is that I can now add a Name to my nested Label control, the bad news is nothing renders! Not in design mode, and not when running the application. No errors are thrown either.
So, my question is: What am I missing? What am I doing wrong?
I am also open to suggestions for other ways to meet my requirements.
Current code
public class MyGrid : UserControl
{
public static readonly DependencyProperty LeftProperty = DependencyProperty.Register("Left", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
public object Left
{
get { return (object)GetValue(LeftProperty); }
set { SetValue(LeftProperty, value); }
}
public static readonly DependencyProperty RightProperty = DependencyProperty.Register("Right", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
public object Right
{
get { return (object)GetValue(RightProperty); }
set { SetValue(RightProperty, value); }
}
Grid MainGrid;
static MyGrid()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyGrid), new FrameworkPropertyMetadata(typeof(MyGrid)));
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
//Create control elements
MainGrid = new Grid();
//add column definitions
ColumnDefinition leftColumn = new ColumnDefinition()
{
Name = "LeftColumn",
Width = new GridLength(300)
};
MainGrid.ColumnDefinitions.Add(leftColumn);
MainGrid.ColumnDefinitions.Add(new ColumnDefinition()
{
Width = GridLength.Auto
});
//add grids and splitter
Grid leftGrid = new Grid();
Grid.SetColumn(leftGrid, 0);
MainGrid.Children.Add(leftGrid);
GridSplitter splitter = new GridSplitter()
{
Name = "Splitter",
Width = 5,
BorderBrush = new SolidColorBrush(Color.FromArgb(255, 170, 170, 170)),
BorderThickness = new Thickness(1, 0, 1, 0)
};
MainGrid.Children.Add(splitter);
Grid rightGrid = new Grid();
Grid.SetColumn(rightGrid, 1);
MainGrid.Children.Add(rightGrid);
//add content presenters
ContentPresenter leftContent = new ContentPresenter();
leftContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Left") { Source = this });
leftGrid.Children.Add(leftContent);
ContentPresenter rightContent = new ContentPresenter();
rightContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Right") { Source = this });
rightGrid.Children.Add(rightContent);
//Set this content of this user control
this.Content = MainGrid;
}
}
After some discussion via comments, it quickly became clear that neither of my attempted solutions was the correct way to go about it. So I set out on a third adventure hoping this one would be the final solution... and it seems it is!
Disclaimer: I do not yet have enough experience with WPF to confidently say that my solution is the best and/or recommended way to do this, only that it definitely works.
First of all create a new custom control: "Add" > "New Item" > "Custom Control (WPF)". This will create a new class that inherits from Control.
In here we put our dependency properties for bind to out content presenters:
public class MyGrid : Control
{
public static readonly DependencyProperty LeftProperty = DependencyProperty.Register("Left", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
public object Left
{
get { return (object)GetValue(LeftProperty); }
set { SetValue(LeftProperty, value); }
}
public static readonly DependencyProperty RightProperty = DependencyProperty.Register("Right", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
public object Right
{
get { return (object)GetValue(RightProperty); }
set { SetValue(RightProperty, value); }
}
static MyGrid()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyGrid), new FrameworkPropertyMetadata(typeof(MyGrid)));
}
}
When you add this class file in Visual Studio, it will automatically create a new "Generic.xaml" file in the project containing a Style for this control, along with a Control Template within that style - this is where we define our control elements...
<Style TargetType="{x:Type MyControls:MyGrid}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MyControls:MyGrid}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="500" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<ContentPresenter x:Name="LeftContent" />
</Grid>
<GridSplitter Width="5" BorderBrush="#FFAAAAAA" BorderThickness="1,0,1,0">
</GridSplitter>
<Grid Grid.Column="1">
<ContentPresenter x:Name="RightContent" />
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The final step is to hook up the bindings for the 2 content presenters, so back to the class file.
Add the following override method to the MyGrid class:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
//Apply bindings and events
ContentPresenter leftContent = GetTemplateChild("LeftContent") as ContentPresenter;
leftContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Left") { Source = this });
ContentPresenter rightContent = GetTemplateChild("RightContent") as ContentPresenter;
rightContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Right") { Source = this });
}
And that's it! The control can now be used in other XAML code like so:
<MyControls:MyGrid>
<MyControls:MyGrid.Left>
<Label x:Name="MyLabel">Something unimportant</Label>
</MyControls:MyGrid.Left>
<MyControls:MyGrid.Right>
<Label>Whatever</Label>
</MyControls:MyGrid.Right>
</MyControls:MyGrid>
Thanks to #NovitchiS for your input, your suggestions were vital in getting this approach to work

Categories