LiveCharts Cartesian Mapping and Configuration with custom Labels - c#

I have plotted the Cartesian chart using X and Y values, to mimic step chart (x values are not consistent). Is it possible to add custom x Labels for each step changed in Y value? Or similar labels like in a bar chart.
var values = new ChartValues<Point>();
Loop:
var point = new Point() { X = entry.DailyXFrom, Y = entry.YValue };
values.Add(point);
point = new Point() { X = entry.DailyXTo, Y = entry.YValue };
values.Add(point);
tempLabels.Add(entry.DailyVolume.ToString());
seriesCollectionDaily = new SeriesCollection
{
new LineSeries
{
Configuration = new CartesianMapper<Point>()
.X(point => point.X)
.Y(point => point.Y)
,
Fill = Brushes.Transparent,
Title = "Series",
Values = values,
PointGeometry = null,
LineSmoothness = 0
}
};
XAxis.Separator.IsEnabled = true;
XAxis.Labels = tempLabels.ToArray();
chart.DataContext = this;
<lvc:CartesianChart Name="chart" Grid.Row="2" Grid.ColumnSpan="2" Series="{Binding seriesCollectionDaily }" >
<lvc:CartesianChart.AxisX >
<lvc:Axis Name="XAxis" Title="" LabelsRotation="0" Foreground="Black" >
<lvc:Axis.Separator>
<lvc:Separator Name="LabelSeparator"></lvc:Separator>
</lvc:Axis.Separator>
</lvc:Axis>
</lvc:CartesianChart.AxisX>
</lvc:CartesianChart>
What I got:
This is what I'm trying to achieve. Is it possible?

You can add sections to your chart axis.
To draw vertical sections you have to define a SectionsCollection of AxisSection items and bind it to Axis.Sections of the x-axis.
By setting AxisSection.Value you can define the position on the axis. Use AxisSection.SectionWidth to define the width of the section. The default is 1, which draws a simple stroke when AxisSection.StrokeThickness is set.
The following example uses StepLineSeries to plot a step chart.
To give an example, it shows two vertical lines placed at the corresponding x-value: a simple line (SectionWidth = 1) and a section where SectionWidth > 1 (e.g., to highlight a range):
Data Model
public class DataModel : INotifyPropertyChanged
{
public DataModel()
{
this.SeriesCollection = new SeriesCollection
{
new StepLineSeries()
{
Configuration = new CartesianMapper<ObservablePoint>()
.X(point => point.X)
.Y(point => point.Y)
.Stroke(point => point.Y > 0.3 ? Brushes.Red : Brushes.LightGreen),
Values = new ChartValues<ObservablePoint>
{
new ObservablePoint(0, 5),
new ObservablePoint(20, 0),
new ObservablePoint(30, 5),
new ObservablePoint(40, 0),
new ObservablePoint(80, 5),
}
}
};
// Add two sections at x=20 and x=30
this.SectionsCollection = new SectionsCollection()
{
new AxisSection()
{
Value = 20,
Stroke = Brushes.Red,
StrokeThickness = 1
},
new AxisSection()
{
Value = 30,
SectionWidth = 50,
Stroke = Brushes.Red,
StrokeThickness = 1
}
};
}
public Func<double, string> LabelFormatter => value => $"{value}ms";
public SeriesCollection SeriesCollection { get; set; }
public SectionsCollection SectionsCollection { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Chart View
<Window>
<Window.DataContext>
<DataModel />
</Window.DataContext>
<CartesianChart Height="500"
Series="{Binding SeriesCollection}">
<CartesianChart.AxisX>
<Axis Sections="{Binding SectionsCollection}"
LabelFormatter="{Binding LabelFormatter}">
<Axis.Separator>
<Separator Step="10" />
</Axis.Separator>
</Axis>
</CartesianChart.AxisX>
<CartesianChart.AxisY>
<Axis>
<Axis.Separator>
<Separator Step="5" />
</Axis.Separator>
</Axis>
</CartesianChart.AxisY>
</CartesianChart>
</Window>

Related

LiveCharts - Connect across missing points

I am using LiveCharts to plot several line charts on the same graph. Some of the charts have missing data points.
Current graph with gaps:
I would like to connect across these gaps:
The goal if possible:
MainWindow.xaml
<Window x:Class="LiveChartsTest.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:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<lvc:CartesianChart Series="{Binding Series}">
<lvc:CartesianChart.AxisX>
<lvc:Axis Title="Date" Labels="{Binding Labels}"/>
</lvc:CartesianChart.AxisX>
</lvc:CartesianChart>
</Grid>
</Window>
MainWindow.xaml.cs
using LiveCharts;
using LiveCharts.Wpf;
using System;
using System.Windows;
namespace LiveChartsTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Create date labels
Labels = new string[10];
for (int i = 0; i < Labels.Length; i++)
{
Labels[i] = DateTime.Now.Add(TimeSpan.FromDays(i)).ToString("dd MMM yyyy");
}
Series = new SeriesCollection
{
new LineSeries
{
Title = "Dataset 1",
Values = new ChartValues<double>
{
4,
5,
7,
double.NaN,
double.NaN,
5,
2,
8,
double.NaN,
6
}
},
new LineSeries
{
Title = "Dataset 2",
Values = new ChartValues<double>
{
2,
3,
4,
5,
6,
3,
1,
4,
5,
3
}
}
};
DataContext = this;
}
public SeriesCollection Series { get; set; }
public string[] Labels { get; set; }
}
}
Is there any way to do this with LiveCharts?
That looks more like a math trigonometry problem, where you have to figure out the coordinates of the missing points and add them to SeriesCollection so you can end up with that flat looing joints between the fragments.
Consider the following explanatory picture based on your graph:
Between the X and Y we have to deduce two points, A and B (we know that we need two in between because we can deduce that from the interval between X and Y or we can simply count the NaNs in the initial collection).
A & B Y coordinates could be easily deduced using what we already know and the angle α.
We are looking to calculate the |BB'| and |AA'| sizes (added to the distance between y and the index should represent the final A and B)
We know basically that: tan(α)= |BB'|/|B'Y| = |AA'|/|A'Y| = |XZ|/|ZY|
For simplicity now let's assume that all intervals in the X-axis and Y-axis are equal 1, I will come back to this later.
Now we do know |XZ|/|ZY|, (xz is the difference between x and y, and zy is basically how many NaNs there is in between), so we can easily calculate |BB'| and |AA'|:
|BB'| = (|XZ|/|ZY|) * |B'Y| (Note that |B'Y| is equal to one since it's a one unit interval)
|AA'| = (|XZ|/|ZY|) * |A'Y| (Note that |A'Y| is equal to two-unit interval )
Here how a basic implementation to what was explained above looks like (the code should be self-explanatory):
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Create date labels
Labels = new string[10];
for (int i = 0; i < Labels.Length; i++)
{
Labels[i] = DateTime.Now.Add(TimeSpan.FromDays(i)).ToString("dd MMM yyyy");
}
var chartValues = new ChartValues<double>
{
4,
5,
7,
double.NaN,
double.NaN,
5,
2,
8,
double.NaN,
6
};
Series = new SeriesCollection
{
new LineSeries
{
Title = "Dataset 1",
Values = ProcessChartValues(chartValues)
},
new LineSeries
{
Title = "Dataset 2",
Values = new ChartValues<double>
{
2,
3,
4,
5,
6,
3,
1,
4,
5,
3
}
}
};
DataContext = this;
}
private ChartValues<double> ProcessChartValues(ChartValues<double> chartValues)
{
var tmpChartValues = new ChartValues<double>();
double bornLeft =0, bornRight=0;
double xz = 0, zy = 0, xy = 0;
bool gapFound = false;
foreach (var point in chartValues)
{
if (!double.IsNaN(point))
{
if (gapFound)
{
// a gap was found and it needs filling
bornRight = point;
xz = Math.Abs(bornLeft - bornRight);
for (double i = zy; i >0; i--)
{
tmpChartValues.Add((xz / zy) * i + Math.Min(bornLeft, bornRight));
}
tmpChartValues.Add(point);
gapFound = false;
zy = 0;
}
else
{
tmpChartValues.Add(point);
bornLeft = point;
}
}
else if(gapFound)
{
zy += 1;
}
else
{
zy += 1;
gapFound = true;
}
}
return tmpChartValues;
}
public SeriesCollection Series { get; set; }
public string[] Labels { get; set; }
}
And here the output:
Now coming back to our interval size, notice how the fragments aren't sharp because of our interval=1 assumption, but on the other hand, this assumption gave the graph some smoothness which is most likely what anyone would be after. If you still need to have sharp fragments, you could explore the LiveChart API to get that interval in pixels which I am not sure they offer (then simply multiply with xz and zy sizes) otherwise you could deduce it from the ActualWidth and ActualHight of the chart drawn area.
As a final note, the code should be extended to handle the NaN points on the edges (you have to either neglect them or define a direction to which the graph should go).

How to handle charts directly in code

I started working with C# and Live Charts a few days ago.
Based on examples found in the internet, I made a very simple graph in order to understand the concepts behind it.
That is what I have (it is working perfectly fine):
public partial class Wdw_graph : Window
{
public SeriesCollection SeriesCollection { get; set; }
public string[] Labels { get; set; }
public Wdw_graph(List<dated_value> serie)
{
InitializeComponent();
SeriesCollection = new SeriesCollection();
ColumnSeries col_serie = new ColumnSeries {
Values = new ChartValues<double>(),
DataLabels = true };
Labels = new string[serie.Count];
for(int i = 0; i < serie.Count; i++)
{
col_serie.Values.Add(serie[i].value);
Labels[i] = serie[i].date;
}
SeriesCollection.Add(col_serie);
DataContext = this;
}
}
<Grid>
<lvc:CartesianChart Series="{Binding SeriesCollection}" >
<lvc:CartesianChart.AxisX>
<lvc:Axis Labels="{Binding Labels}" LabelsRotation="80">
<lvc:Axis.Separator>
<lvc:Separator IsEnabled="False" Step="1"></lvc:Separator>
</lvc:Axis.Separator>
</lvc:Axis>
</lvc:CartesianChart.AxisX>
<lvc:CartesianChart.AxisY>
<lvc:Axis Title="Sold Apps"></lvc:Axis>
</lvc:CartesianChart.AxisY>
</lvc:CartesianChart>
</Grid>
As you can see, I have 'SeriesCollection' and 'Labels' handled in the class code and them they are bound to the XAML.
I would like to know if it is possible to use the same approach to handle other graph elements, such as the 'AxisX'. If so, how can I do it?
In this post "Change the format of the axis tick labels in LiveCharts" there is a code that shows what I would like to do:
cartesianChart2.AxisX.Add(new Axis
{
Name = "xAxis",
Title = "DateTime",
FontSize = 22,
Foreground = System.Windows.Media.Brushes.Black,
MinValue = 0,
MaxValue = _amountValues,
});
But I could not reproduce that. I can't figure out where the 'cartesianChart2' comes from.
Well, I learned how to refer to the XAML elements in the class code.
I added 'x:Name="something"' to the CartesianChart and removed everything else (not using 'Binding' anymore):
<Grid>
<lvc:CartesianChart x:Name="cartesian_chart">
</lvc:CartesianChart>
</Grid>
Then, in the class code I inserted the axis and serie:
public partial class Wdw_graph : Window
{
public Wdw_graph(List<dated_value> serie)
{
InitializeComponent();
cartesian_chart.AxisX.Add(new Axis
{
Name = "xAxis",
Title = "Date and Time",
FontSize = 20,
Foreground = System.Windows.Media.Brushes.Black,
MinValue = 0,
MaxValue = serie.Count,
Labels = new String[serie.Count],
});
cartesian_chart.AxisY.Add(new Axis
{
Name = "yAxis",
Title = "Currency",
FontSize = 20,
Foreground = System.Windows.Media.Brushes.Black,
MinValue = 0,
MaxValue = 10,
});
cartesian_chart.AxisX[0].Separator.Step = 1;
cartesian_chart.AxisX[0].LabelsRotation = 80;
ColumnSeries col_serie = new ColumnSeries {
Values = new ChartValues<double>(),
DataLabels = true };
for(int i = 0; i < serie.Count; i++)
{
col_serie.Values.Add(serie[i].value);
cartesian_chart.AxisX[0].Labels[i] = serie[i].date;
}
cartesian_chart.Series.Add(col_serie);
DataContext = this;
}
}
I really preferred to handle the graph directly from the code, instead of using the XAML.
Can you tell me what are the drawbacks of this approach?

LiveCharts: how to plot skewed data (i.e exponential y-axis) in column series wpf

How to set the Y-axis interval exponentially in column series?
new ColumnSeries
{
Fill = new SolidColorBrush(Color.FromRgb(30,130,173)),
Width = 100,
MaxColumnWidth = 100,
Values = new ChartValues<double> {500,30,10},
DataLabels = true,
LabelPoint = point => point.Y +"",
FontSize = 20
}
You can configure your y-axis with a logarithmic scale - there's an explanation of how to do this on the Live Charts site https://lvcharts.net/App/examples/v1/wpf/Logarithmic%20Scale
Here's an example adapted for a column series:
public SeriesCollection SeriesCollection { get; set; }
public MainWindow()
{
InitializeComponent();
var mapper = Mappers.Xy<double>()
.X((value, index) => index)
.Y((value, index) => Math.Log(value, 10));
SeriesCollection = new SeriesCollection(mapper)
{
new ColumnSeries
{
Values = new ChartValues<double>{500,30,10}
}
};
DataContext = this;
}
and the XAML:
<Grid>
<lvc:CartesianChart Series="{Binding SeriesCollection}">
<lvc:CartesianChart.AxisY>
<lvc:LogarithmicAxis Base="10" />
</lvc:CartesianChart.AxisY>
</lvc:CartesianChart>
</Grid>

Set Rectangle width by Items in list C# WPF

How do I set the width of a rectangle, by dividing the number of Items in a list via a user control in C#, using WPF has to be in code behind and not using Xaml:
public partial class UserControl1 : UserControl
{
private List<Color> colours;
private int Highest;
private int Lowest;
private int Median;
public int Width {get; set;}
private Data mydata;
public Data data
{
get
{
return mydata;
}
set
{
mydata = value;
BindData();
}
}
public UserControl1()
{
colours = new List<Color>() { Colors.Red, Colors.White, Colors.Blue};
InitializeComponent();
if (Width == 0)
{
Width = 300;
}
}
private void BindData()
{
//work out rectangle width
Highest = Convert.ToInt32(mydata.Values.Max());
Lowest = Convert.ToInt32(mydata.Values.Min());
Median = Convert.ToInt32(mydata.Values.Average());
foreach (double item in mydata.Values)
{
//create rectangle of width specified
SetWidth();
}
}
private void SetWidth()
{
//divide number of items in list by total to define Width
}
private Color GetColourFromValue(int totalNumber)
{
var numOfColours = colours.Count;
if (totalNumber >= numOfColours)
{
return colours[totalNumber % numOfColours];
}
return colours[totalNumber];
}
private Color GetGradeColour(byte itemNumber, byte totalNumberItems, Color ItemColour)
{
byte multipler = (byte)(200 / totalNumberItems);
byte a = (byte)((itemNumber + 1) * multipler);
return Color.FromArgb(a, ItemColour.R, ItemColour.G, ItemColour.B);
}
private Rectangle CreateRectangle(double Value, int Width, Color ItemColor)//double value, width)
{
//create rectangle
var rectangle = new Rectangle()
{
//Fill = new SolidColorBrush(ItemColor)
};
//give rectangle width
return rectangle;
}
}
If you want a set of controls to automatically share the horizontal space of a parent UserControl then use a Grid control with columns. This is much easier than trying the calculate the widths manually.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0"/>
<Rectangle Grid.Column="1"/>
<Rectangle Grid.Column="2"/>
<Rectangle Grid.Column="3"/>
</Grid>
Code:
var grid = new Grid
{
ColumnDefinitions =
{
new ColumnDefinition(),
new ColumnDefinition(),
new ColumnDefinition(),
new ColumnDefinition()
}
};
var rectangle1 = new Rectangle { Fill = Brushes.Blue };
rectangle1.SetValue(Grid.ColumnProperty, 0);
grid.Children.Add(rectangle1);
var rectangle2 = new Rectangle { Fill = Brushes.Red };
rectangle2.SetValue(Grid.ColumnProperty, 1);
grid.Children.Add(rectangle2);
var rectangle3 = new Rectangle { Fill = Brushes.Green };
rectangle3.SetValue(Grid.ColumnProperty, 2);
grid.Children.Add(rectangle3);
var rectangle4 = new Rectangle { Fill = Brushes.Yellow };
rectangle4.SetValue(Grid.ColumnProperty, 3);
grid.Children.Add(rectangle4);

How do you get Canvas.SetTop to work on ItemsCollection items?

Below is the complete program (XAML and code behind). When started you will see:
When you click [Show/Hide] you see:
When you uncheck Home and Documents and click [Apply] you see:
As you can see, the Home and Documents folder disappear, but the other folders do not move up. Looking at the two list boxes you see three items per line, folder label, the value for Canvas.SetTop, and True: Visibility.Visible or False: Visibility.Collapsed. The Original Placement list box are the original values. Each time you click [Apply] the New Placement List box shows that new values. The Visibility value gets applied and the result is visible, either the folder is there or it is not. However, applying the Canvas.SetTop does not work. And you can see that the values have changed for the Canvas.SetTop. And, if you look at the code, you see that it is applied to the same UIElement as the Visibility is applied to:
var f = Me[i];
Canvas.SetTop(iec, f.Top); // Why does this not work and
iec.Visibility = f.FolderVisible; // this does work?
I need the folders that are visible to move up to take the place of those that are collapsed. Canvas.SetTop is not working. How can I get the Folders to move?
Here is the Solution Explorer pane so that you know what is needed:
Here is the XAML:
<Window x:Class="FolderTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:FolderTest"
Title="Folder Test"
Width="450"
Height="410">
<Grid Margin="10">
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<!-- Folder Show/Hide Button -->
<Button Name="FolderOptions" Content="Show/Hide" FontSize="8" FontWeight="Bold" Width="50" Height="20" Margin="116, 0, 0, 0" Click="Event_ShowHide_ButtonClick" />
<!-- Folders -->
<Grid>
<Canvas Name="xFolders"
Width="170"
Height="309"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="0,10,0,0">
<Canvas.Resources>
<local:Folders x:Key="myFolders" />
</Canvas.Resources>
<ItemsControl ItemsSource="{Binding Source={StaticResource myFolders}}">
<ItemsControl.Template>
<ControlTemplate>
<ItemsPresenter />
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
</DataTemplate.Resources>
<Canvas Name="FolderCanvas"
Tag="{Binding Path=Number}"
Visibility="{Binding Path=FolderVisible}">
<Path Data="{Binding Path=FolderPath}"
Stroke="{Binding Path=Brush}"
Fill="{Binding Path=Brush}"/>
<StackPanel Orientation="Horizontal"
Canvas.Left="5"
Canvas.Top="2">
<TextBlock Text="{Binding Path=Label}"
FontSize="12"
Margin="20, 0, 0, 0" />
</StackPanel>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Top"
Value="{Binding Top}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Canvas>
</Grid>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="10, 0, 0, 0">
<TextBlock Text="Original Placemet:" />
<ListBox Name="OriginalPlacement" />
<Button Name="Refresh" Content="New Placemet:" Margin="0, 10, 0, 0" Click="Event_OriginalPlacement_ButtonClick" />
<ListBox Name="NewPlacement" />
</StackPanel>
</StackPanel>
</Grid>
</Window>
Here is the C# Code Behind:
using System;
using System.Collections.Generic;
using System.Windows.Media;
using System.Windows.Controls;
using System.Windows;
using System.Collections.ObjectModel;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace FolderTest
{
public struct GD
{
public static MainWindow MainWindow = null;
public static Button Refresh = null;
}
public partial class MainWindow : Window
{
public MainWindow()
{
GD.MainWindow = this;
InitializeComponent();
Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
GD.Refresh = Refresh;
Folders.Placement(ref OriginalPlacement);
}
private void Event_ShowHide_ButtonClick(object sender, RoutedEventArgs e)
{
var pu = Folders.FolderShowPopUp();
pu.PlacementTarget = FolderOptions;
pu.IsOpen = true;
}
private void Event_OriginalPlacement_ButtonClick(object sender, RoutedEventArgs e)
{
Folders.Placement(ref NewPlacement);
}
}
public class Folder
{// Specification for each folder tab seen on the left.
public int Number { get; set; } // Folder sequence number top to bottom starging with 1.
public string Label { get; set; } // The label that appears on the folder tab itself.
public double Top { get; set; } // Position of the upper left corner of this folder.
public SolidColorBrush Brush { get; set; } // Solid Color Brush.
public string FolderPath { get; set; } // Contains the geometric path to draw this folder and its tab.
public Visibility FolderVisible { get; set; } // This folder's Show/Hide flag.
public Folder(int Number, string Label, double Top, SolidColorBrush Brush, string FolderPath, Visibility FolderVisible)
{
this.Number = Number;
this.Label = Label;
this.Top = Top;
this.Brush = Brush;
this.FolderPath = FolderPath;
this.FolderVisible = FolderVisible;
}
}
public class Folders : ObservableCollection<Folder>
{
public static ObservableCollection<Folder> Me = null;
private static string[] Labels = new string[]
{
"Personal",
"Health",
"Finances",
"Home",
"Employment",
"Insurance",
"Documents",
"Contacts",
"Journal"
};
private static Dictionary<string, Tuple<bool, bool>> LabelData = new Dictionary<string, Tuple<bool, bool>>()
{// Label Show Hidable
{Labels[0], new Tuple<bool, bool>(true, false)},
{Labels[1], new Tuple<bool, bool>(true, true)},
{Labels[2], new Tuple<bool, bool>(true, true)},
{Labels[3], new Tuple<bool, bool>(true, true)},
{Labels[4], new Tuple<bool, bool>(true, true)},
{Labels[5], new Tuple<bool, bool>(true, true)},
{Labels[6], new Tuple<bool, bool>(true, true)},
{Labels[7], new Tuple<bool, bool>(true, false)},
{Labels[8], new Tuple<bool, bool>(true, true)}
};
private static string[] FolderColors = new string[]
{
"FF36579E",
"FFDF2024",
"FF16A146",
"FF00B2D4",
"FFF47B20",
"FF9F1F63",
"FF13A89E",
"FFB7B7E7",
"FF50CAF5",
"FFAA9E74",
"FF86787D",
"FF36D146",
};
private static byte[] ARGBColor = new byte[4]; // Byte array for the folder top color.
private static void ColorString2ARGB(ref byte[] bytes, string Hex)
{// Converts 8 char hex string to 4 byte array.
for (int i = 0; i < 8; i += 2)
bytes[i / 2] = Convert.ToByte(Hex.Substring(i, 2), 16);
}
private static int colorIndex = -1; // Initial value of the colorIndex.
private static string NextColor()
{// Returns a 8 char string containing the next top FolderColor. If at end, cycle to beginning.
colorIndex++;
if (colorIndex >= FolderColors.Length) colorIndex = 0;
return FolderColors[colorIndex];
}
private class FolderShow
{
public string Label { get; set; }
public bool Show { get; set; }
public bool Hidable { get; set; }
public FolderShow(string label, bool show, bool hidable)
{
Label = label;
Show = show;
Hidable = hidable;
}
}
private static List<FolderShow> FolderShowList = null;
public static Popup FolderShowPopUp()
{
var pu = new Popup()
{
Placement = PlacementMode.Right,
AllowsTransparency = true,
StaysOpen = false
};
var b = new Border()
{
BorderThickness = new Thickness(2),
BorderBrush = new SolidColorBrush() { Color = Colors.Black },
CornerRadius = new CornerRadius(5),
Background = new SolidColorBrush() { Color = Colors.White }
};
var sp = new StackPanel()
{
Orientation = Orientation.Vertical,
Margin = new Thickness(10)
};
var tb = new TextBlock()
{
Text = "Checked Folders will be Displayed: ",
FontSize = 16,
FontWeight = FontWeights.Bold,
};
sp.Children.Add(tb);
foreach (var fs in FolderShowList)
{
var cb = new CheckBox()
{
Content = fs.Label,
IsChecked = fs.Show,
IsEnabled = fs.Hidable,
FontSize = 14,
FontWeight = FontWeights.Bold,
Margin = new Thickness(0, 5, 0, 0)
};
cb.Click += new RoutedEventHandler(Event_CheckBoxFolderList_Click);
sp.Children.Add(cb);
}
var bp = new StackPanel()
{
Orientation = Orientation.Horizontal,
Margin = new Thickness(0, 5, 0, 0)
};
var ba = new Button() { Content = "Apply", Width = 50, Height = 25, BorderBrush = new SolidColorBrush(Colors.Transparent), Tag = pu };
ba.Click += new RoutedEventHandler(Event_ApplyFolderList_Click);
bp.Children.Add(ba);
var bc = new Button() { Content = "Cancel", Width = 50, Height = 25, BorderBrush = new SolidColorBrush(Colors.Transparent), Tag = pu, Margin = new Thickness(7, 0, 0, 0) };
bc.Click += new RoutedEventHandler(Event_CancelFolderList_Click);
bp.Children.Add(bc);
sp.Children.Add(bp);
var tbm = new TextBlock()
{
Text = "Disabled folders cannot be hidden.",
Margin = new Thickness(0, 5, 0, 0),
FontSize = 12,
Foreground = new SolidColorBrush() { Color = Colors.Red }
};
sp.Children.Add(tbm);
b.Child = sp;
pu.Child = b;
return pu;
}
private static void Event_CheckBoxFolderList_Click(object sender, RoutedEventArgs e)
{
var cb = (CheckBox)e.Source;
foreach (var fs in FolderShowList)
{
if (fs.Label == cb.Content as string)
{
fs.Show = (bool)cb.IsChecked;
break;
}
}
}
private static void Event_CancelFolderList_Click(object sender, RoutedEventArgs e)
{
((Popup)((Button)e.Source).Tag).IsOpen = false;
}
private static void Event_ApplyFolderList_Click(object sender, RoutedEventArgs e)
{
foreach (var fs in FolderShowList)
{
var sh = LabelData[fs.Label];
LabelData[fs.Label] = new Tuple<bool, bool>(fs.Show, sh.Item2);
}
((Popup)((Button)e.Source).Tag).IsOpen = false;
int p = 0;
foreach (var f in Me)
{
var fs = LabelData[f.Label].Item1 == true;
f.Top = p * folderPositionFactor;
f.FolderVisible = fs ? Visibility.Visible : Visibility.Collapsed;
if (fs) p += 1;
}
foreach (ItemsControl ic in GD.MainWindow.xFolders.Children)
{
for (int i = 0; i < ic.Items.Count; i++)
{
var ie = (UIElement)ic.ItemContainerGenerator.ContainerFromIndex(i);
var iec = Controls.FindChildByType<Canvas>(ie, "FolderCanvas");
if (iec != null)
{
var f = Me[i];
Canvas.SetTop(iec, f.Top); // Why does this not work and
iec.Visibility = f.FolderVisible; // this does work?
}
}
break;
}
GD.Refresh.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
}
public static void Placement(ref ListBox lb)
{
lb.Items.Clear();
foreach (ItemsControl ic in GD.MainWindow.xFolders.Children)
{
for (int i = 0; i < ic.Items.Count; i++)
{
var ie = (UIElement)ic.ItemContainerGenerator.ContainerFromIndex(i);
var iec = Controls.FindChildByType<Canvas>(ie, "FolderCanvas");
if (iec != null)
{
var f = Me[i];
lb.Items.Add(f.Label + ": " + f.Top.ToString() + ", " + (f.FolderVisible == Visibility.Visible ? "True" : "False"));
}
}
break;
}
}
public Folders()
{// The constructor, initializes itself by creating a folder for each label in Labels
FolderShowList = new List<FolderShow>();
for (int i = 0; i < Labels.Length; i++)
{
string n = Labels[i];
bool h = LabelData[Labels[i]].Item2;
bool s = h == true ? LabelData[Labels[1]].Item1 == true : true;
FolderShowList.Add(new FolderShow(n, s, h));
}
SetFolders();
Me = this;
}
const double folderPositionFactor = 31; // 21.80;
public void SetFolders()
{
SolidColorBrush[] scb = new SolidColorBrush[Labels.Length]; // Hold the linear solid color brush to assign to the folder.
Color[] pointerColor = new Color[Labels.Length]; // Hold the color to assign to the folder's solid color brush.
for (int i = 0; i < Labels.Length; i++)
{// Create a solid color brush for each folder.
ColorString2ARGB(ref ARGBColor, NextColor()); // Color into byte array.
Color TmpColor = Color.FromArgb(ARGBColor[0], ARGBColor[1], ARGBColor[2], ARGBColor[3]); // Create top color.
pointerColor[i] = TmpColor;
SolidColorBrush TmpSCB = new SolidColorBrush() { Color = TmpColor };
scb[i] = TmpSCB; // Assign the solid color brush.
}
// All is ready to create the individual folders.
const string folderHeight = "56"; // "44";
Brush FontColor = Brushes.Black; // Initial font color for labels.
string fp = "M0,7 A7,7 90 0 1 7,0 L100,0 105,18 150,18 150,FH 0,FH Z".Replace("FH", folderHeight); // Initial geometric path for folder design.
int afp = 0; // Actual Folder Position.
for (int i = 0; i < Labels.Length; i++)
{// Create the individual folders.
bool fs = FolderShowList[i].Show;
Add(new Folder(
i + 1, // Folder sequence count.
Labels[i], // Folder label.
afp * folderPositionFactor, // Position of top of folder.
scb[i % scb.Length], // Solid color brush.
fp, // Geometric path for folder design.
fs ? Visibility.Visible : Visibility.Collapsed // User Hidden.
)
);
if (fs) afp += 1;
if (i == 0)
{// First folder created, now set values for remaining folders.
FontColor = Brushes.White;
fp = "M0,25 A7,7 90 0 1 7,18 L13,18 18,0 100,0 105,18 150,18 150,FH 0,FH Z".Replace("FH", folderHeight);
}
}
}
}
public static class Controls
{
public static T FindChildByType<T>(DependencyObject parent, string childName) where T : DependencyObject
{
if (parent == null) return null; // No parent, get out of here.
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
T childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChildByType<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
}
}
Thanks for any help that you can provide.
I finally figured out the answer, and it is very simple.
This is the code to be changed (the center line only):
var f = Me[i];
Canvas.SetTop(iec, f.Top); // Why does this not work
iec.Visibility = f.FolderVisible; // and this does work?
This is the new code (only the center line is changed, actually, only one character is removed):
var f = Me[i];
Canvas.SetTop(ie, f.Top);
iec.Visibility = f.FolderVisible;
That is, change Canvas.SetTop(iec, f.Top); to Canvas.SetTop(ie, f.Top); that is, only change iec to ie and all works fine.

Categories

Resources