I have wpf solution, where I have created UserControl for trending. This UserControl is used in MainWindow.
The path of trend is painted on method showData() of current class ChartControl. But because I want to have actual picture of path related to main window size, I have add SizeChanged event where this showData() method is called.
My code for event here:
private void OnResize(object sender, SizeChangedEventArgs e)
{
this.showData();
}
Edit:
private List<ChartData> data = new List<ChartData>();
public void showData()
{
double maxVal = this.maxVal();
double minVal = this.minVal();
TimeSpan timeSpan = new TimeSpan();
timeSpan = this.maxTime() - this.minTime();
double stepSize = Area.ActualWidth / timeSpan.TotalSeconds;
setLabels();
Area.Children.Clear();
for (int i = 1; i < this.data.Count; i++)
{
Line lineHorizont = new Line();
lineHorizont.StrokeThickness = 2;
lineHorizont.Stroke = Brushes.Red;
lineHorizont.X1 = (this.data[i].X - this.minTime()).TotalSeconds * stepSize;
lineHorizont.Y1 = Math.Abs(((this.data[i - 1].Y - minVal) / (maxVal - minVal) * Area.ActualHeight) - Area.ActualHeight);
lineHorizont.X2 = lineHorizont.X1;
lineHorizont.Y2 = Math.Abs(((this.data[i].Y - minVal) / (maxVal - minVal) * Area.ActualHeight) - Area.ActualHeight);
Area.Children.Add(lineHorizont);
Line lineVertical = new Line();
lineVertical.StrokeThickness = 2;
lineVertical.Stroke = Brushes.Red;
lineVertical.X1 = (this.data[i - 1].X - this.minTime()).TotalSeconds * stepSize;
lineVertical.Y1 = Math.Abs(((this.data[i - 1].Y - minVal) / (maxVal - minVal) * Area.ActualHeight) - Area.ActualHeight);
lineVertical.X2 = (this.data[i].X - this.minTime()).TotalSeconds * stepSize;
lineVertical.Y2 = lineVertical.Y1;
Area.Children.Add(lineVertical);
}
//Draw cross coordinator
coordX1.StrokeThickness = 1;
coordX1.Stroke = Brushes.Black;
coordX1.X1 = 0;
coordX1.Y1 = Mouse.GetPosition(Area).Y;
coordX1.X2 = Area.ActualWidth;
coordX1.Y2 = coordX1.Y1;
Area.Children.Add(coordX1);
coordX2.StrokeThickness = 1;
coordX2.Stroke = Brushes.Black;
coordX2.X1 = Mouse.GetPosition(Area).X;
coordX2.Y1 = 0;
coordX2.X2 = coordX2.X1;
coordX2.Y2 = Area.ActualHeight;
Area.Children.Add(coordX2);
}
public double maxVal()
{
List<double> data = new List<double>();
for (int i = 0; i < this.data.Count; i++)
{
data.Add(this.data[i].Y);
}
return data.Max<double>();
}
edit2:
xaml content of Main Window
<Grid Margin="0">
<lib:ChartControl x:Name="Trend" Margin="0" Width="Auto" Height="Auto"/>
</Grid>
xaml content of ChartControl
<Grid Grid.Column="1" Margin="0" Grid.Row="1" Background="Black" Cursor="Cross" PreviewMouseMove="OnMouseMove">
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Rectangle Fill="#FFF1F1F1" Grid.RowSpan="10"/>
<Rectangle Fill="#FFD4D4D4"/>
<Rectangle Fill="#FFD4D4D4" Grid.Row="2"/>
<Rectangle Fill="#FFD4D4D4" Grid.Row="4"/>
<Rectangle Fill="#FFD4D4D4" Grid.Row="6"/>
<Rectangle Fill="#FFD4D4D4" Grid.Row="8"/>
<Canvas x:Name="Area" Grid.RowSpan="10"/>
</Grid>
After start of program everything is working fine and according expectations, but I am getting Exception in View Designer in Visual studio, which is eliminating any design changes.
In the OnResize event handler you must test that the current instance is not in design mode.
System.ComponentModel.DesignerProperties.GetIsInDesignMode(this)
if you have no data then return...then no more design exception !!
private void OnResize(object sender, SizeChangedEventArgs e) {
if(!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
this.showData();
}
List<double> data = new List<double>();
for (int i = 0; i < this.data.Count; i++)
{
data.Add(this.data[i].Y);
}
return data.Max<double>();
Please, please, please don't name your local variables the same as your outer-scope variables.
As for why this is failing, it's obvious:
this.data is empty when this is called, as such, when you try to perform data.Max<double>() you get an exception.
You're calling MaxVal() at the very beginning of ShowData() and as far as I can see, there's no real discernible place you're adding any ChartData to the data list.
Related
My application calculates points by a given formula. With a small interval, new points are added to the chart. With the slider I can adjust the values of these points.
For optimization purposes, unnecessary points begin to be deleted from the list. But after deletion, the value of the next point starts to be calculated incorrectly when you move the slider.
Before points deleting (gif)
After points deleting (gif)
How can this problem be solved?
Class:
using System.Windows;
using LiveCharts;
using LiveCharts.Wpf;
using LiveCharts.Defaults;
using System.Windows.Media;
using System.Threading.Tasks;
using System;
using System.Threading;
namespace TestChartApp
{
public partial class MainWindow : Window
{
SeriesCollection series = new SeriesCollection();
ChartValues<ObservableValue> observableValues = new ChartValues<ObservableValue>();
LineSeries lineSeries = new LineSeries
{
Stroke = Brushes.Blue,
Fill = Brushes.Transparent,
PointGeometry = null
};
double currentStep = 0;
public MainWindow()
{
InitializeComponent();
lineSeries.Values = observableValues;
series.Add(lineSeries);
myChart.Series = series;
myChart.DataTooltip = null;
myChart.Hoverable = false;
Task.Factory.StartNew(AddValues);
}
private void AddValues()
{
Application.Current.Dispatcher.Invoke(() =>
{
ObservableValue value = new ObservableValue(sAmplitude.Value * Math.Sin(2 * Math.PI * 0.25 * currentStep));
currentStep += 0.06;
observableValues.Add(value);
if (observableValues.Count > 100)
{
SetAxisLimits(observableValues.Count);
}
if (observableValues.Count > 150)
{
observableValues.RemoveAt(0);
}
});
Thread.Sleep(35);
Task.Factory.StartNew(AddValues);
}
private void SetAxisLimits(double value)
{
Axis axis = myChart.AxisX[0];
axis.MinValue += value - axis.MaxValue;
axis.MaxValue = value;
}
private void ChangeObservableValues()
{
int j = 0;
for (double i = 0.0; j < observableValues.Count; i += 0.06)
{
observableValues[j++].Value = sAmplitude.Value * Math.Sin(2 * Math.PI * 0.25 * i);
}
}
private void sAmplitude_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (lineSeries.Values != null)
{
ChangeObservableValues();
}
}
}
}
XAML:
<Window x:Class="TestChartApp.MainWindow"
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:TestChartApp"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.8*"/>
<RowDefinition Height="0.2*"/>
</Grid.RowDefinitions>
<lvc:CartesianChart x:Name="myChart" DisableAnimations="True">
<lvc:CartesianChart.AxisX>
<lvc:Axis MaxValue="100" MinValue="0" Labels="" Unit="1">
<lvc:Axis.Separator>
<lvc:Separator Step="20">
<lvc:Separator.Stroke>
<SolidColorBrush Color="Gray" />
</lvc:Separator.Stroke>
</lvc:Separator>
</lvc:Axis.Separator>
</lvc:Axis>
</lvc:CartesianChart.AxisX>
<lvc:CartesianChart.AxisY>
<lvc:Axis MaxValue="100" MinValue="-100" Labels="">
<lvc:Axis.Separator>
<lvc:Separator>
<lvc:Separator.Stroke>
<SolidColorBrush Color="Gray" />
</lvc:Separator.Stroke>
</lvc:Separator>
</lvc:Axis.Separator>
</lvc:Axis>
</lvc:CartesianChart.AxisY>
</lvc:CartesianChart>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Slider x:Name="sAmplitude" HorizontalAlignment="Stretch" Margin="30 30 30 0" Grid.Row="1" VerticalAlignment="Top" Maximum="100" Value="50" LargeChange="10" ValueChanged="sAmplitude_ValueChanged"/>
</Grid>
</Grid>
</Window>
It looks like you are calculating the wrong values when updating the old data.
ChangeObservableValues is calculating its own currentSteps value, always starting from 0, which is wrong as the original value for the current data point is bigger :
originalCurrentStepsOfCurrentDataPoint =
absoluteCurrentDataPointPosition * 0.06.
To allow easier refactoring and also to eliminate a potential error source, the value 0.06 should be a constant field or read-only property.
private const double Step = 0.06;
private void ChangeObservableValues()
{
// Because the collection only contains the latest 150 values,
// we can calculate the absolute position using the collection's Count
var restoredCurrentStep =
this.currentStep - this.observableValues.Count * MainWindow.Step;
for (int index = 0; index < observableValues.Count; index++)
{
observableValues[index].Value =
sAmplitude.Value * Math.Sin(2 * Math.PI * 0.25 * restoredCurrentStep);
restoredCurrentStep += MainWindow.Step;
}
}
I been working with winforms, so my knowledge of WFP is non existent, this is something i am trying to test.
In code I am generating few buttons and placing them on Canvas. Than after clik on any button, i am moving that button around, and after second click button should stay at the position where mouse cursor was when clicked.
If mouse cursor go outside canvas then button will stop follow it.
My problem is, that button is moving, but only when mouse cursor is over that button or any other control, but it is not moving while mouse cursor is traveling over Canvas.
XAML
<Window x:Class="WpfTestDrag.MainWindow"
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:WpfTestDrag"
mc:Ignorable="d"
Title="MainWindow" Height="522" Width="909">
<Grid>
<Grid.ColumnDefinitions >
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="130*"/>
<ColumnDefinition Width="33*"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions >
<RowDefinition Height="40" />
<RowDefinition Height="*" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Canvas Grid.Column="1" Grid.Row="1" x:Name="cnvTest" Width="auto" Height="auto" PreviewMouseMove="CnvTest_PreviewMouseMove"/>
<TextBlock x:Name="txbStatus" Grid.Column="1" Grid.Row="2" Grid.ColumnSpan="2"/>
</Grid>
</Window>
C#
public partial class MainWindow : Window
{
Button bts;
Boolean isUsed = false;
public MainWindow()
{
InitializeComponent();
CreateButtons();
}
private void CreateButtons()
{
var xPos = 10.0;
var yPos = 15.0;
var Rnd = new Random();
for (int i = 0; i < 3; i++)
{
var btn = new Button();
btn.Name = "btn" + i;
btn.Content = "Button - " + i;
btn.Tag = "Tag" + i;
btn.Width = 150;
btn.Height = 150;
btn.Click += Btn_Click;
Canvas.SetLeft(btn, xPos);
Canvas.SetTop(btn, yPos);
cnvTest.Children.Add(btn);
xPos = xPos + btn.Width + Rnd.Next(-15,40);
yPos = yPos + btn.Height + Rnd.Next(-15, 40);
}
}
private void Btn_Click(object sender, RoutedEventArgs e)
{
bts = sender as Button;
if (isUsed == false)
{
isUsed = true;
}
else
{
isUsed = false;
}
}
private void CnvTest_PreviewMouseMove(object sender, MouseEventArgs e)
{
Point p = Mouse.GetPosition(cnvTest);
if (isUsed == true)
{
Canvas.SetLeft(bts, p.X);
Canvas.SetTop(bts, p.Y);
txbStatus.Text = bts.Name.ToString() + " isUsed:" + isUsed.ToString() + " -> xPos:" + p.X.ToString() + " yPos:" + p.Y.ToString();
}
}
}
Should I use something else than Canvas for this?
You should set the Background property of the Canvas to Transparent (or any other Brush) for it to respond to the mouse events:
<Canvas Grid.Column="1" Grid.Row="1" x:Name="cnvTest" Width="auto" Height="auto" PreviewMouseMove="CnvTest_PreviewMouseMove"
Background="Transparent"/>
I am binding window DragMove event to the border control for moving the window. Propert is local:EnableDragHelper.EnableDrag="True" you can check design below.
<Border Grid.Row="0" BorderThickness="1" BorderBrush="Black" Background="#467EAF" Name="borderHeader" local:EnableDragHelper.EnableDrag="True">
<StackPanel Grid.Row="0" VerticalAlignment="Center">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="100"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Content="{Binding InspectionHistoryModel.CurrentDateTime,Mode=TwoWay}" FontWeight="Bold" Foreground="White" FontSize="18" Margin="5,0,0,0"></Label>
<TextBlock Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left" Name="popupTaregetextblock" Margin="10,0,0,0" VerticalAlignment="Center">
<Hyperlink FontSize="20" Foreground="White" Command="{Binding ShowHideHeaderPopupCommand}" CommandParameter="onDuty"><TextBlock Text="{Binding InspectionHistoryModel.HeaderDutyText, Mode=TwoWay}" VerticalAlignment="Center" FontWeight="Bold" FontSize="18" Foreground="White"> </TextBlock></Hyperlink>
</TextBlock>
</Grid>
</StackPanel>
</Border>
Comamnd of Hyperlink (which put in inside of the border) is not working. How to possible this? Drag Code is
private static void UIElementOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
{
var uiElement = sender as UIElement;
if (uiElement != null)
{
if (mouseEventArgs.LeftButton == MouseButtonState.Pressed)
{
DependencyObject parent = uiElement;
int avoidInfiniteLoop = 0;
// Search up the visual tree to find the first parent window.
while ((parent is Window) == false)
{
parent = VisualTreeHelper.GetParent(parent);
avoidInfiniteLoop++;
if (avoidInfiniteLoop == 1000)
{
// Something is wrong - we could not find the parent window.
return;
}
}
var window = parent as Window;
if (window.WindowState == WindowState.Maximized)
{
var mouseX = mouseEventArgs.GetPosition(window).X;
var width = window.RestoreBounds.Width;
var x = mouseX - width / 2;
if (x < 0)
{
x = 0;
}
else
if (x + width > SystemParameters.PrimaryScreenWidth)
{
x = SystemParameters.PrimaryScreenWidth - width;
}
window.WindowState = WindowState.Normal;
window.Left = x;
window.Top = 0;
// window.Width = window.ActualWidth;
// window.Height = window.ActualHeight;
// window.Left = 0;
// window.Top = 0;
// window.WindowStartupLocation = WindowStartupLocation.Manual;
// window.WindowState = WindowState.Normal;
}
window.DragMove();
}
}
}
I'm trying to do something which is as simple as adding elements to a 6x7 grid through code behind. The grid is defined in xaml as following
<Grid x:Name="CalendarGrid" Grid.Row="1" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
I have a function called InitializeCalendar which would populate the grid with buttons. Somehow I cant figure out how to specify row and column to which I want to add the button. How to I reference the row and column of CalendarGrid?
void InitializeCalendar()
{
for (int i = 0; i < 6; i++)
{
for (int j = 0; i < 7; j++)
{
butArray[i + 1, j + 1] = new Button();
//CalendarGrid. I cant find function to specify the row and button
}
}
}
I found that there is a property called ColumnProperty.
butArray[i + 1, j + 1].SetValue(Grid.ColumnProperty, 0);
But then there are so many Grids in my page. How do I reference the CalendarGrid? Any solutions?
Thanks,
You can use Grid.SetRow (msdn) and Grid.SetColumn (msdn) methods:
void InitializeCalendar()
{
for (int i = 0; i < 6; i++)
{
for (int j = 0; j < 7; j++)
{
butArray[i, j] = new Button();
butArray[i, j].Content = (i).ToString() + (j).ToString();
CalendarGrid.Children.Add(butArray[i, j]);
Grid.SetRow(butArray[i, j], i);
Grid.SetColumn(butArray[i, j], j);
}
}
}
You have to add the button to the CalendarGrid. Try the following way:
CalendarGrid.Children.Add(butArray[i + 1, j + 1]);
butArray[i + 1, j + 1].SetValue(Grid.ColumnProperty, columnNumber);
butArray[i + 1, j + 1].SetValue(Grid.RowProperty, rowNumber);
-progressbar always 0%
-the window is froozen (while DoWork r.)
-if System.threading.thread.sleep(1) on - works perfectly
whats the problem?
private void btnNext_Click(object sender, RoutedEventArgs e)
{
this._worker = new BackgroundWorker();
this._worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
long current = 1;
long max = generalMaxSzam();
for (int i = 1; i <= 30; i++)
{
for (int j = i+1; j <= 30; j++)
{
for (int c = j+1; c <= 30; c++)
{
for (int h = c+1; h <= 30; h++)
{
for (int d = h+1; d <= 30; d++)
{
int percent = Convert.ToInt32(((decimal)current / (decimal)max) * 100);
this._worker.ReportProgress(percent);
current++;
//System.Threading.Thread.Sleep(1); - it works well
}
}
}
}
}
};
this._worker.WorkerReportsProgress = true;
this._worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
this.Close();
};
this._worker.ProgressChanged += delegate(object s, ProgressChangedEventArgs args)
{
this.statusPG.Value = args.ProgressPercentage;
};
this._worker.RunWorkerAsync();
}
<Window x:Class="SzerencsejatekProgram.Create"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Létrehozás" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Height="500" Width="700">
<DockPanel>
<Button DockPanel.Dock="Right" Name="btnNext" Width="80" Click="btnNext_Click">Tovább</Button>
<StatusBar DockPanel.Dock="Bottom">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem Grid.Column="1">
<TextBlock Name="statusText"></TextBlock>
</StatusBarItem>
<StatusBarItem Grid.Column="2">
<ProgressBar Name="statusPG" Width="80" Height="18" IsEnabled="False" />
</StatusBarItem>
<StatusBarItem Grid.Column="3">
<Button Name="statusB" IsCancel="True" IsEnabled="False">Cancel</Button>
</StatusBarItem>
</StatusBar>
</DockPanel>
</Window>
Your code runs a very tight loop and at its center it calls ReportProgress().
This means that your MessageQueue is swamped with requests to execute the Progress updates.
If you build some delay (Thread.Sleep(100)) into the Bgw thread you will see the responsiveness improve.
A more practical solution is to move the reporting out to the outer loop. In your case:
for (int i = 1; i <= 30; i++)
{
int percent = (i * 100) / 30;
_worker.ReportProgress(percent);
for(int j = 0; ....)
....
}
If you only have 1 loop, build in a delay: 'if ((counter % 100) == 0) ...`
Your target here is the user, aim for between 10 and 100 calls to Reportprogress.
Your anonymous method for the ProgressChanged event will run on UI thread. since you're reporting frequent progress it will be queued up in by the dispatcher and blocks the UI.
if (current++ % onePercent == 0)
{
int percent = Convert.ToInt32(((decimal)current / (decimal)max) * 100);
this._worker.ReportProgress(percent, new WorkerUserState { current = current, max = max });
}
this works well.