WPF Custom ChartLine Performance Issue - c#

We've developed a simple WPF UserControl which is a ChartLine that is usually supposed to display 512 values in a range of -100 to 100.
The chart works, however, the chart needs to have its values cleared and updated every 1 second and it is taking over a second (1.4~~seconds) to simply render all of its values.
After this frustrated attempt, I tried to use old DynamicDataDisplay (D3) from Microsoft which is supposed to be faster, but the performance impact was quite the same, also taking more than a second to update the 512 values on the screen.
Below is my code, I do believe there may be some caching technique, lower bitmap resolution or something to help achieve my goal.
XAML:
<UserControl x:Class="IHM.OsciloscopeGraphic"
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:IHM"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="740" Loaded="UserControl_Loaded">
<Grid x:Name="gdMain">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:OsciloscopeGraphic}}, Path=TitleGreen}" HorizontalAlignment="Right" HorizontalContentAlignment="Center" Margin="10, 0" FontSize="18" Width="115" Background="Green"/>
<Label Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:OsciloscopeGraphic}}, Path=TitleLightBlue}" FontSize="18" Margin="10, 0" HorizontalAlignment="Left" HorizontalContentAlignment="Center" Background="LightBlue" Grid.Column="1" Width="115"/>
<Grid Name="gdChartArea" Grid.Row="1" Grid.ColumnSpan="2" >
<Border BorderBrush="Black" BorderThickness="1" Margin="30, 10, 10, 30"/>
<Canvas x:Name="cnvChart" Margin="30, 10, 10, 30">
<Canvas.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#4C000080" Offset="1"/>
<GradientStop Color="#4C7F7FFF"/>
</LinearGradientBrush>
</Canvas.Background>
</Canvas>
</Grid>
</Grid>
C# Code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Shapes;
namespace IHM
{
public partial class OsciloscopeGraphic : UserControl
{
#region Properties
/// <summary>
/// If steps Lines are 0, will divide the grid equally by the number in lines grid
/// </summary>
public int LinesGrid { get; set; }
/// <summary>
/// If steps Columns are 0, will divide the grid equally by the number in lines grid
/// </summary>
public int ColumnsGrid { get; set; }
public int StepsLines { get; set; }
public int StepsColumns { get; set; }
public int MaxHorizontal { get; set; }
public int MaxVertical { get; set; }
public int MinHorizontal { get; set; }
public int MinVertical { get; set; }
public static readonly DependencyProperty TitleGreenProperty =
DependencyProperty.Register("TitleGreen", typeof(string), typeof(BarGraphicSplitted), new UIPropertyMetadata("TRS"));
[Bindable(true)]
public string TitleGreen
{
get { return (string)GetValue(TitleGreenProperty); }
set { SetValue(TitleGreenProperty, value); }
}
public static readonly DependencyProperty TitleLightBlueProperty =
DependencyProperty.Register("TitleLightBlue", typeof(string), typeof(BarGraphicSplitted), new UIPropertyMetadata("FRT"));
[Bindable(true)]
public string TitleLightBlue
{
get { return (string)GetValue(TitleLightBlueProperty); }
set { SetValue(TitleLightBlueProperty, value); }
}
#endregion Properties
#region Local Fields/Variables
private bool initialized = false;
private int Quantidade
{
get { return (Math.Abs(this.MaxHorizontal - this.MinHorizontal) + 1); }
}
#endregion Local Fields/Variables
public OsciloscopeGraphic()
{
InitializeComponent();
this.MaxHorizontal = 255;
this.MinHorizontal = 0;
this.MaxVertical = 100;
this.MinVertical = -100;
this.LinesGrid = 0;
this.ColumnsGrid = 0;
this.StepsColumns = 10;
this.StepsLines = 10;
}
#region Private Local/Methods
private Line CreateGridLine()
{
Line lm = new Line();
lm.Stroke = Brushes.Black;
lm.StrokeThickness = 1;
lm.StrokeDashArray = new DoubleCollection() { 1, 4 };
lm.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
return lm;
}
private Line CreateHorizontalGridLine(Point start, double length)
{
Line ln = CreateGridLine();
//It has the same value because the line will be a vertical line
ln.X1 = start.X;
ln.X2 = start.X + length;
ln.Y1 = start.Y;
ln.Y2 = start.Y;
return ln;
}
private Line CreateHorizontalScaleLine(Point start)
{
Line l = CreateScaleLine();
l.X1 = start.X;
l.X2 = start.X - 5;
l.Y1 = start.Y;
l.Y2 = start.Y;
return l;
}
private Line CreateScaleLine()
{
Line l = new Line();
l.Stroke = Brushes.Black;
l.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
return l;
}
private Line CreateVerticalGridLine(Point start, double length)
{
Line ln = CreateGridLine();
//It has the same value because the line will be a vertical line
ln.X1 = start.X;
ln.X2 = start.X;
ln.Y1 = start.Y;
ln.Y2 = start.Y + length;
return ln;
}
private Line CreateVerticalScaleLine(Point start)
{
Line l = CreateScaleLine();
l.X1 = start.X;
l.X2 = start.X;
l.Y1 = start.Y;
l.Y2 = start.Y + 5;
return l;
}
private void DrawGrid(Grid grid, Canvas chart)
{
bool makeBySteps = true;
if ((this.StepsColumns == 0) || (this.StepsLines == 0))
{
makeBySteps = false;
if ((this.LinesGrid == 0) || (this.ColumnsGrid == 0))
throw new DivideByZeroException();
}
//get canvas absolute position
var getPos = chart.TransformToVisual(grid);
Point XYpos = getPos.Transform(new Point(0, 0));
//draw the lines
double actualWidth = (chart.ActualWidth);
double initialPosition = (XYpos.X + 1);
double length = this.MaxHorizontal - this.MinHorizontal + 1;
double stepLegend = (makeBySteps) ? this.StepsColumns : length / Convert.ToDouble(this.ColumnsGrid);
int counter = (makeBySteps) ? ((int)length) / this.StepsColumns : this.ColumnsGrid;
double step = (makeBySteps) ? (actualWidth / length) * this.StepsColumns : (actualWidth / this.ColumnsGrid);
length = Math.Abs(length);
double remainder = 0d;
for (int i = 0; i <= counter; i++)
{
//vertical gridlines
double steps = i * step;
Point start = new Point(initialPosition + steps, XYpos.Y);
Line Lm = CreateVerticalGridLine(start, chart.ActualHeight);
grid.Children.Add(Lm);
//vertical scale lines
Point startScale = new Point(initialPosition + steps, XYpos.Y + chart.ActualHeight);
Line LineScale = CreateVerticalScaleLine(startScale);
grid.Children.Add(LineScale);
//bottom labels
Label lb = new Label();
lb.Width = 20;
lb.Height = 20;
lb.Padding = new Thickness(0);
lb.HorizontalContentAlignment = HorizontalAlignment.Center;
lb.ClipToBounds = false;
//this garantes that it will consider the reminder of divisions
double numero = this.MinHorizontal + (i * stepLegend);
remainder += numero - Math.Round(numero);
numero = Math.Round(numero);
if (remainder > 1)
{
remainder -= 1;
numero += 1;
}
else if (remainder < -1)
{
remainder += 1;
numero -= 1;
}
lb.Content = numero;
grid.Children.Add(lb);
lb.HorizontalAlignment = HorizontalAlignment.Left;
lb.VerticalAlignment = VerticalAlignment.Top;
//TODO: big coment explaining in details the line bellow
lb.Margin = new Thickness((XYpos.X - 10) + steps, XYpos.Y + chart.ActualHeight + 5, 0, 0);
}
initialPosition = XYpos.Y;
double actualHeight = (chart.ActualHeight);
length = this.MaxVertical - this.MinVertical + 1;
stepLegend = (makeBySteps) ? this.StepsLines : length / Convert.ToDouble(this.LinesGrid);
counter = (makeBySteps) ? ((int)length) / this.StepsLines : this.LinesGrid;
step = (makeBySteps) ? (actualHeight / length) * this.StepsLines : (actualHeight / this.LinesGrid);
//initialPosition = (makeBySteps) ? initialPosition + ((actualHeight / length) * (length % this.StepsLines)) : initialPosition;
length = Math.Abs(length);
remainder = 0d;
for (int i = 0; i <= counter; i++)
{
double steps = i * step;
Point start = new Point(XYpos.X, actualHeight + initialPosition - steps);
//horizontal gridlines
Line lm = CreateHorizontalGridLine(start, actualWidth);
grid.Children.Add(lm);
//horizontal scale lines
Line l = CreateHorizontalScaleLine(start);
grid.Children.Add(l);
//side labels
Label lb = new Label();
lb.Width = 30;
lb.Height = 20;
lb.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Right;
lb.Padding = new Thickness(0);
lb.VerticalContentAlignment = VerticalAlignment.Center;
lb.ClipToBounds = false;
//this garantes that it will consider the reminder of divisions
double numero = this.MinVertical + (i * stepLegend);
remainder += numero - Math.Round(numero);
numero = Math.Round(numero);
if (remainder > 1)
{
remainder -= 1;
numero += 1;
}
else if (remainder < -1)
{
remainder += 1;
numero -= 1;
}
lb.Content = numero;
grid.Children.Add(lb);
lb.HorizontalAlignment = HorizontalAlignment.Left;
lb.VerticalAlignment = VerticalAlignment.Top;
//TODO: big coment explaining in details the line bellow
lb.Margin = new Thickness(XYpos.X - 37, start.Y - 10, 0, 0);
}
}
private void DrawGrid()
{
this.DrawGrid(gdChartArea, cnvChart);
}
private void DrawLine(List<int> p_values, SolidColorBrush cor)
{
Polyline cl = new Polyline();
cl.Stroke = cor;
cl.StrokeThickness = 2;
cl.StrokeLineJoin = PenLineJoin.Round;
//cl.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
double stepHorizontal = cnvChart.ActualWidth / ((this.MaxHorizontal - this.MinHorizontal) + 1);
double stepVertical = cnvChart.ActualHeight / ((this.MaxVertical - this.MinVertical) + 1);
for (int i = 0; i < p_values.Count; i++)
{
int val = p_values[i];
double x = (stepHorizontal * i);
double y = cnvChart.ActualHeight - ((val - this.MinVertical) * stepVertical);
cl.Points.Add(new Point(x, y));
}
cnvChart.Children.Add(cl);
}
private void DrawLineGreen(List<int> p_values)
{
DrawLine(p_values, Brushes.Green);
}
private void DrawLineLightBlue(List<int> p_values)
{
DrawLine(p_values, Brushes.LightBlue);
}
private List<int> GetRandomValues()
{
int quantidade = this.Quantidade;
List<int> lsValues = new List<int>(quantidade);
int seed = 0;
long ticks = DateTime.Now.Ticks;
while (ticks > int.MaxValue)
{
ticks -= int.MaxValue;
}
seed = Convert.ToInt32(ticks);
Random ran = new Random(seed);
for (int i = 0; i < quantidade; i++)
{
int randomValue = ran.Next(this.MinVertical, this.MaxVertical);
lsValues.Add(randomValue);
}
return lsValues;
}
#endregion Private Local/Methods
#region Public Methods
public void Clear()
{
this.cnvChart.Children.Clear();
}
public void UpdateGraphValues()
{
UpdateGraphValues(GetRandomValues(), GetRandomValues());
}
public void UpdateGraphValues(List<int> p_frontValues, List<int> p_backValues)
{
//Clear current graphic values.
Clear();
DrawLineGreen(p_frontValues);
DrawLineLightBlue(p_backValues);
}
#endregion Public Methods
#region Window Events
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
if (!initialized)
{
DrawGrid();
UpdateGraphValues();
initialized = true;
}
}
#endregion Window Events
}
}
To test the graph in the conditions I'd like you can simply instantiate`
private OsciloscopeGraphic graphicOscNormal = new OsciloscopeGraphic()
{
MinHorizontal = 0,
MaxHorizontal = 255,
MinVertical = -100,
MaxVertical = 100
};
and inside a timer you may call `graphicOscNormal.UpdateGraphValues() ` which will furfill the graphic with random values for testing purposes.
Later these values will come from serial port which is already implemented.
NOTE: I've also tried to replace the high level PolyLine for DrawingVisual and DrawingContext.DrawLine, BUT the performance has NOT changed!
NOTE2: I'm using C#/WPF and .NET 4.0 (VS 2010).
Thanks in advanced, Luís.

The (biggest) problem is your random number generator - it is extremely inefficient. Try :
private Random ran = new Random(0);
private List<int> GetRandomValues()
{
int quantidade = this.Quantidade;
List<int> lsValues = new List<int>(quantidade);
for (int i = 0; i < quantidade; i++)
{
int randomValue = ran.Next(this.MinVertical, this.MaxVertical);
lsValues.Add(randomValue);
}
return lsValues;
}
When optimizing, it pays to profile.
If you want really, really fast rendering then you almost have to go back to GDI. For example - update your Canvas (cnvChart) to use this FastCanvas :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows;
using System.Windows.Interop;
using System.Runtime.InteropServices;
using System.Drawing.Drawing2D;
namespace WpfApplication1
{
class FastCanvas : Canvas
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateFileMapping(IntPtr hFile,
IntPtr lpFileMappingAttributes,
uint flProtect,
uint dwMaximumSizeHigh,
uint dwMaximumSizeLow,
string lpName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject,
uint dwDesiredAccess,
uint dwFileOffsetHigh,
uint dwFileOffsetLow,
uint dwNumberOfBytesToMap);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool UnmapViewOfFile(IntPtr lbBaseAddress);
protected System.Drawing.Graphics GDIGraphics;
protected InteropBitmap interopBitmap = null;
protected InteropBitmap buffBitmap = null;
private const uint FILE_MAP_ALL_ACCESS = 0xF001F;
private const uint PAGE_READWRITE = 0x04;
private int bpp = PixelFormats.Bgra32.BitsPerPixel / 8;
protected IntPtr MapViewPointer;
public struct ScopeLine
{
public SolidColorBrush lineBrush;
public List<Point> linePoints;
}
public List<ScopeLine> Lines = new List<ScopeLine>();
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
if (Lines.Count() > 0)
{
ImageSource drIs = null;
if (interopBitmap == null)
{
uint byteCount = (uint)((int)this.ActualWidth * (int)this.ActualHeight * bpp);
var fileMappingPointer = CreateFileMapping(new IntPtr(-1), IntPtr.Zero, PAGE_READWRITE, 0, byteCount, null);
this.MapViewPointer = MapViewOfFile(fileMappingPointer, FILE_MAP_ALL_ACCESS, 0, 0, byteCount);
PixelFormat format = PixelFormats.Bgra32;
var stride = (int)((int)this.ActualWidth * format.BitsPerPixel / 8);
this.interopBitmap = Imaging.CreateBitmapSourceFromMemorySection(fileMappingPointer,
(int)this.ActualWidth,
(int)this.ActualHeight,
format,
stride,
0) as InteropBitmap;
this.GDIGraphics = GetGdiGraphics(MapViewPointer);
}
GDIGraphics.FillRectangle(System.Drawing.Brushes.Transparent,
new System.Drawing.Rectangle(0, 0,
(int)this.ActualWidth,
(int)this.ActualHeight));
foreach (ScopeLine dLine in Lines)
{
var pointCount = dLine.linePoints.Count();
Color lpColour;
lpColour = dLine.lineBrush.Color;
System.Drawing.Color lp2Colour;
lp2Colour = System.Drawing.Color.FromArgb(lpColour.A,
lpColour.R,
lpColour.G,
lpColour.B);
System.Drawing.Pen lpPen = new System.Drawing.Pen(lp2Colour, 1.5f);
System.Drawing.PointF newPoint = new System.Drawing.PointF((float)dLine.linePoints[0].X,
(float)dLine.linePoints[0].Y);
for (int i = 0; i < pointCount - 1; i++)
{
System.Drawing.PointF newPoint1 = new System.Drawing.PointF((float)dLine.linePoints[i + 1].X,
(float)dLine.linePoints[i + 1].Y);
GDIGraphics.DrawLine(lpPen, newPoint, newPoint1);
newPoint = newPoint1;
}
}
var bmpsrc = interopBitmap.GetAsFrozen();
if (bmpsrc == null || bmpsrc.CheckAccess())
{
drIs = (System.Windows.Media.Imaging.BitmapSource)bmpsrc;
}
else
{
//Debug.WriteLine("No access to TheImage");
}
dc.DrawImage(drIs, new Rect(this.RenderSize));
}
}
private System.Drawing.Graphics GetGdiGraphics(IntPtr mapViewPointer)
{
System.Drawing.Graphics gdiGraphics;
System.Drawing.Bitmap gdiBitmap;
gdiBitmap = new System.Drawing.Bitmap((int)this.ActualWidth,
(int)this.ActualHeight,
(int)this.ActualWidth * bpp,
System.Drawing.Imaging.PixelFormat.Format32bppArgb,
mapViewPointer);
gdiGraphics = System.Drawing.Graphics.FromImage(gdiBitmap);
gdiGraphics.CompositingMode = CompositingMode.SourceCopy;
gdiGraphics.CompositingQuality = CompositingQuality.HighSpeed;
gdiGraphics.SmoothingMode = SmoothingMode.HighSpeed;
return gdiGraphics;
}
}
}
and change your DrawLine as :
private void DrawLine(List<int> p_values, SolidColorBrush cor)
{
double stepHorizontal = cnvChart.ActualWidth / ((this.MaxHorizontal - this.MinHorizontal) + 1);
double stepVertical = cnvChart.ActualHeight / ((this.MaxVertical - this.MinVertical) + 1);
List<Point> pts = new List<Point>();
for (int i = 0; i < p_values.Count; i++)
{
int val = p_values[i];
double x = (stepHorizontal * i);
double y = cnvChart.ActualHeight - ((val - this.MinVertical) * stepVertical);
pts.Add(new Point(x,y));
}
FastCanvas.ScopeLine newLine;
newLine.lineBrush = cor;
newLine.linePoints = pts;
cnvChart.Lines.Add(newLine);
}
and UpdateValues to :
public void UpdateGraphValues(List<int> p_frontValues, List<int> p_backValues)
{
cnvChart.Lines.Clear();
DrawLineGreen(p_frontValues);
DrawLineLightBlue(p_backValues);
cnvChart.InvalidateVisual();
}
Using GDI like this the same graph can update in real time (easily > 30fps for 512 points) as compared to about 5-7fps using WPF rendering.

First thing you can do in order to improve performance is replacing all Labels with TextBlocks. TextBlocks are drawn much faster! Freeze all freezables (custom Brushes for example) as is described HERE. Maybe THIS, THIS and THIS can help too, these are threads about Polyline optimization. I hope i helped:)

Related

My Widget stops updating after some period of time

I have a widget made up of 2 TextViews, 2 ImageViews, and 2 TextClocks. I am trying to update the widget using a ScreenListener (code below) and an alarm. I want all views to update when the screen is turned On. Also, a repeating alarm is set when the screen is turned On. The alarm is used to trigger the update of the battery level indicator (an ImageView). The image for the battery level indicator is generated using the ProgressRing (code below). The alarm is cancelled when the screen is turned Off. The TextClocks take care of themselves.
Everything works when debugging using the emulator or my phone. The various components update when I turn the screen On and Off. However, when I install the widget on my phone, the widget stops updating after a period of time. Initially, I can turn the screen On and Off and everything updates. And, I can plug (or unplug) the phone and the alarm will update the battery level indicator. But, after some period of time (I think with the screen Off), updating stops. Turning the screen On no longer updates anything and, I beleive, the alarm is no longer set. (Note: The TextClock continue to work and show the correct time.)
When debugging, I have seen the instances of the ScreenLister and ProgressRing become null. So, I've included checks for this and make new instances when it happens. This doesn't seem like the right solution. Should this be occurring?
Is the system killing my widget? Is there a way around this, if so? Is my AppWidgetProvider somehow losing it's connection to the RemoteViews?
Thanks for any help. And, let me know if you need more information.
P.S. I'm currently building for Android 9.0. And, I'm new to Android programming.
AppWidgetProvider
namespace ClockCalBattery
{
[BroadcastReceiver(Enabled = true)]
[IntentFilter(new string[] { "android.appwidget.action.APPWIDGET_UPDATE", Intent.ActionUserPresent})]
[MetaData("android.appwidget.provider", Resource = "#xml/appwidgetprovider")]
public class CCBWidget : AppWidgetProvider, ScreenStateListener
{
public static ProgressRing progressRing = new ProgressRing();
private static String ACTION_UPDATE_BATTERY = "com.lifetree.clockcalbattery.UPDATE-BATTERY";
public static ScreenListener mScreenListener;
public static int n_alarms = 0;
public override void OnReceive(Context context, Intent intent)
{
base.OnReceive(context, intent);
if (intent.Action == ACTION_UPDATE_BATTERY)
{
update_batteryLevel(context);
if (mScreenListener == null)
{
mScreenListener = new ScreenListener(context);
mScreenListener.begin(this);
}
if (progressRing == null)
{
progressRing = new ProgressRing();
}
}
}
public override void OnEnabled(Context context)
{
base.OnEnabled(context);
if (mScreenListener == null)
{
mScreenListener = new ScreenListener(context);
mScreenListener.begin(this);
}
if (progressRing == null)
{
progressRing = new ProgressRing();
}
update_batteryLevel(Application.Context);
update_nextAlarm(Application.Context);
}
public override void OnDisabled(Context context)
{
base.OnDisabled(context);
}
public override void OnUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
base.OnUpdate(context, appWidgetManager, appWidgetIds);
mScreenListener = new ScreenListener(context);
progressRing = new ProgressRing();
var me = new ComponentName(context, Java.Lang.Class.FromType(typeof(CCBWidget)).Name);
var widgetView = BuildRemoteViews(context, appWidgetIds);
appWidgetManager.UpdateAppWidget(me, widgetView);
}
private RemoteViews BuildRemoteViews(Context context, int[] appWidgetIds)
{
RemoteViews widgetView = new RemoteViews(context.PackageName, Resource.Layout.CCBWidget);
update_TodaysDate(widgetView);
update_nextAlarm(context, widgetView);
update_batteryLevel(widgetView);
return widgetView;
}
private void update_TodaysDate(RemoteViews widgetView)
{
string today = DateTime.Today.ToString("D");
widgetView.SetTextViewText(Resource.Id.longDate, today);
}
private void update_nextAlarm(Context context, RemoteViews widgetView)
{
AlarmManager am = (AlarmManager)context.GetSystemService(Context.AlarmService);
AlarmManager.AlarmClockInfo alarmInfo = am.NextAlarmClock;
if (alarmInfo != null)
{
long time = alarmInfo.TriggerTime;
DateTime alarmDateTime = new DateTime(1970, 1, 1).AddMilliseconds(time).ToLocalTime();
string alarmTime = alarmDateTime.ToString("ddd ");
alarmTime += alarmDateTime.ToString("t");
widgetView.SetTextViewText(Resource.Id.textView_alarmTime, alarmTime);
widgetView.SetImageViewResource(Resource.Id.imageView_alarmIcon, Resource.Drawable.whiteOnBlack_clock);
}
else
{
widgetView.SetTextViewText(Resource.Id.textView_alarmTime, "");
widgetView.SetImageViewResource(Resource.Id.imageView_alarmIcon, Resource.Drawable.navigation_empty_icon);
}
}
private void update_nextAlarm(Context mContext)
{
RemoteViews widgetView = new RemoteViews(mContext.PackageName, Resource.Layout.CCBWidget);
update_nextAlarm(mContext, widgetView);
var me = new ComponentName(mContext, Java.Lang.Class.FromType(typeof(CCBWidget)).Name);
var awm = AppWidgetManager.GetInstance(mContext);
awm.UpdateAppWidget(me, widgetView);
}
private void update_batteryLevel(RemoteViews widgetView)
{
progressRing.setProgress((int)(Battery.ChargeLevel * 100.0));
progressRing.drawProgressBitmap();
widgetView.SetImageViewBitmap(Resource.Id.progressRing, progressRing.ring);
}
private void update_batteryLevel(Context mContext)
{
RemoteViews widgetView = new RemoteViews(mContext.PackageName, Resource.Layout.CCBWidget);
update_batteryLevel(widgetView);
var me = new ComponentName(mContext, Java.Lang.Class.FromType(typeof(CCBWidget)).Name);
var awm = AppWidgetManager.GetInstance(mContext);
awm.UpdateAppWidget(me, widgetView);
}
public static void turnUpdateAlarmOnOff(Context context, bool turnOn, int time)
{
AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);
Intent intent = new Intent(context, typeof(CCBWidget));
intent.SetAction(ACTION_UPDATE_BATTERY);
PendingIntent pendingIntent = PendingIntent.GetBroadcast(context, 0, intent, 0);
if (turnOn)
{
// Add extra 1 sec because sometimes ACTION_BATTERY_CHANGED is called after the first alarm
alarmManager.SetInexactRepeating(AlarmType.ElapsedRealtime, SystemClock.ElapsedRealtime() + 1000, time * 1000, pendingIntent);
n_alarms++;
}
else
{
alarmManager.Cancel(pendingIntent);
n_alarms--;
}
}
public void onScreenOn()
{
if (mScreenListener == null)
{
mScreenListener = new ScreenListener(Application.Context);
mScreenListener.begin(this);
}
if (progressRing == null)
{
progressRing = new ProgressRing();
}
update_batteryLevel(Application.Context);
update_nextAlarm(Application.Context);
turnUpdateAlarmOnOff(Application.Context, true, 60);
}
public void onScreenOff()
{
turnUpdateAlarmOnOff(Application.Context, false, 1);
}
public void onUserPresent()
{
//Console.WriteLine("onUserPresent");
}
}
}
ScreenListener
namespace ClockCalBattery
{
public class ScreenListener
{
private Context mContext;
private ScreenBroadcastReceiver mScreenReceiver;
private static ScreenStateListener mScreenStateListener;
public ScreenListener(Context context)
{
mContext = context;
mScreenReceiver = new ScreenBroadcastReceiver();
}
/**
* screen BroadcastReceiver
*/
private class ScreenBroadcastReceiver : BroadcastReceiver
{
private String action = null;
public override void OnReceive(Context context, Intent intent)
{
action = intent.Action;
if (Intent.ActionScreenOn == action)
{ // screen on
mScreenStateListener.onScreenOn();
}
else if (Intent.ActionScreenOff == action)
{ // screen off
mScreenStateListener.onScreenOff();
}
else if (Intent.ActionUserPresent == action)
{ // unlock
mScreenStateListener.onUserPresent();
}
}
}
/**
* begin to listen screen state
*
* #param listener
*/
public void begin(ScreenStateListener listener)
{
mScreenStateListener = listener;
registerListener();
getScreenState();
}
/**
* get screen state
*/
private void getScreenState()
{
PowerManager manager = (PowerManager)mContext
.GetSystemService(Context.PowerService);
if (manager.IsInteractive)
{
if (mScreenStateListener != null)
{
mScreenStateListener.onScreenOn();
}
}
else
{
if (mScreenStateListener != null)
{
mScreenStateListener.onScreenOff();
}
}
}
/**
* stop listen screen state
*/
public void unregisterListener()
{
mContext.UnregisterReceiver(mScreenReceiver);
}
/**
* regist screen state broadcast
*/
private void registerListener()
{
IntentFilter filter = new IntentFilter();
filter.AddAction(Intent.ActionScreenOn);
filter.AddAction(Intent.ActionScreenOff);
filter.AddAction(Intent.ActionUserPresent);
mContext.ApplicationContext.RegisterReceiver(mScreenReceiver, filter);
//mContext.RegisterReceiver(mScreenReceiver, filter);
}
public interface ScreenStateListener
{// Returns screen status information to the caller
void onScreenOn();
void onScreenOff();
void onUserPresent();
}
}
}
ProgressRing
namespace ClockCalBattery
{
public class ProgressRing
{
private int max = 100;
public int progress;
private Path path = new Path();
Color color = new Color(50, 50, 255, 255);
private Paint paint;
private Paint mPaintProgress;
private RectF mRectF;
private Paint batteryLevelTextPaint;
private Paint batteryStateTextPaint;
private String batteryLevelText = "0%";
private String batteryStateText = null;
private BatteryState bs;
private Rect textBounds = new Rect();
private int centerY;
private int centerX;
private float swipeAndgle = 0;
public Bitmap ring;
private int bitmapWidth = 70;
private int bitmapHeight = 70;
public ProgressRing()
{
progress = -1;
paint = new Paint();
paint.AntiAlias = true;
paint.StrokeWidth = 1;
paint.SetStyle(Paint.Style.Stroke);
paint.Color = color;
mPaintProgress = new Paint();
mPaintProgress.AntiAlias = true;
mPaintProgress.SetStyle(Paint.Style.Stroke);
mPaintProgress.StrokeWidth = 5;
mPaintProgress.Color = color;
batteryLevelTextPaint = new Paint();
batteryLevelTextPaint.AntiAlias = true;
batteryLevelTextPaint.SetStyle(Paint.Style.Fill);
batteryLevelTextPaint.Color = color;
batteryLevelTextPaint.StrokeWidth = 1;
batteryStateTextPaint = new Paint();
batteryStateTextPaint.AntiAlias = true;
batteryStateTextPaint.SetStyle(Paint.Style.Fill); ;
batteryStateTextPaint.Color = color;
batteryStateTextPaint.StrokeWidth = 1;
//batteryStateTextPaint.SetTypeface(Typeface.Create(Typeface.Default, TypefaceStyle.Bold));
Init_ring();
}
private void Init_ring()
{
int viewWidth = bitmapWidth;
int viewHeight = bitmapHeight;
float radius = (float)(bitmapHeight / 2.0);
path.Reset();
centerX = viewWidth / 2;
centerY = viewHeight / 2;
path.AddCircle(centerX, centerY, radius, Path.Direction.Cw);
float smallCirclRadius = radius - (float)(0.1 * radius);
path.AddCircle(centerX, centerY, smallCirclRadius, Path.Direction.Cw);
//mRectF = new RectF(0, 0, viewWidth, viewHeight);
mRectF = new RectF(centerX - smallCirclRadius, centerY - smallCirclRadius, centerX + smallCirclRadius, centerY + smallCirclRadius);
batteryLevelTextPaint.TextSize = radius * 0.5f;
batteryStateTextPaint.TextSize = radius * 0.30f;
}
internal void setProgress(int progress)
{
this.progress = progress;
int percentage = progress * 100 / max;
swipeAndgle = percentage * 360 / 100;
batteryLevelText = percentage + "%";
batteryStateText = null;
bs = Battery.State;
if (bs == BatteryState.Charging)
batteryStateText = "Charging";
else if (percentage > 99)
batteryStateText = "Full";
else if (percentage < 15)
batteryStateText = "Low";
}
internal void drawProgressBitmap()
{
ring = Bitmap.CreateBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.Argb8888);
Canvas c = new Canvas(ring);
byte r = 0;
byte g = 0;
byte b = 0;
byte a = 255;
hls2rgb(swipeAndgle * (120.0 / 360.0), 1, 128, ref r, ref g, ref b);
paint.Color = new Color(r, g, b, a);
mPaintProgress.Color = new Color(r, g, b, a);
batteryLevelTextPaint.Color = new Color(r, g, b, a);
batteryStateTextPaint.Color = new Color(r, g, b, a);
c.DrawArc(mRectF, 270, swipeAndgle, false, mPaintProgress);
drawTextCentred(c);
}
private void drawTextCentred(Canvas c)
{
batteryLevelTextPaint.GetTextBounds(batteryLevelText, 0, batteryLevelText.Length, textBounds);
c.DrawText(batteryLevelText, centerX - textBounds.ExactCenterX(), centerY - textBounds.ExactCenterY(), batteryLevelTextPaint);
if (batteryStateText != null)
{
batteryStateTextPaint.GetTextBounds(batteryStateText, 0, batteryStateText.Length, textBounds);
c.DrawText(batteryStateText, centerX - textBounds.ExactCenterX(), centerY - textBounds.ExactCenterY() + 15, batteryStateTextPaint);
}
}
public void hls2rgb(double color_wheel_angle, double tlen, byte ilum,
ref byte color_r, ref byte color_g, ref byte color_b)
{
double rlum, rm1, rm2;
if (ilum > 255) ilum = 255;
if (ilum < 0) ilum = 0;
if (tlen > 1) tlen = 1;
if (tlen < 0) tlen = 0;
rlum = (double)ilum / 255;
if (rlum < 0.5)
rm2 = rlum * (1.0 + tlen);
else
rm2 = rlum + tlen - (rlum * tlen);
rm1 = (2.0 * rlum) - rm2;
if (tlen == 0)
{
color_r = ilum;
color_g = ilum;
color_b = ilum;
}
else
{
color_g = (byte)(value(rm1, rm2, color_wheel_angle + 120) * 255);
color_b = (byte)(value(rm1, rm2, color_wheel_angle) * 255);
color_r = (byte)(value(rm1, rm2, color_wheel_angle - 120) * 255);
}
return;
}
public double value(double r1, double r2, double angle)
{
double value;
angle = angle - 120;
if (angle > 360) angle = angle - 360;
if (angle < 0) angle = angle + 360;
if (angle < 60)
value = r1 + (r2 - r1) * angle / 60;
else if (angle < 180)
value = r2;
else if (angle < 240)
value = r1 + (r2 - r1) * (240 - angle) / 60;
else
value = r1;
return (value);
}
}
}

Detect if the TIFF image is Horizontal or Vertical - C#

Im using WinForms. In my Form i have a picturebox and a next button. I use this picturebox to display tiff images and i use the next button to navigate to the next page. The tiff documents are multipage images. The document I'm trying to view has a horizontal images and a vertical images like the example below. If its horizontal i want to size it (1100, 800), but if its vertical i want to size it (800, 1100). How do i do this? currently this is what i have but its not a good solution.
System.Drawing.Image img = System.Drawing.Image.FromFile(path_lbl.Text);
if (img.Height > img.Width)
{
pictureBox1.Width = 800;
pictureBox1.Height = 1300;
}
else
{
pictureBox1.Width = 1300;
pictureBox1.Height = 800;
}
I currently use this approach but this doesn't work because if the first image is vertical the if-statement will always execute the first condition pictureBox1.size(1300 , 800); With this method, if the next image is horizontal the condition will not ever re-size it horizontally.
Example Tiff image
http://www.filedropper.com/verticalandhorizontal
Quick Test Code
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Windows.Forms;
namespace Demo
{
class TestForm : Form
{
public TestForm()
{
var panel = new Panel { Dock = DockStyle.Top, BorderStyle = BorderStyle.FixedSingle };
openButton = new Button { Text = "Open", Top = 8, Left = 16 };
prevButton = new Button { Text = "Prev", Top = 8, Left = 16 + openButton.Right };
nextButton = new Button { Text = "Next", Top = 8, Left = 16 + prevButton.Right };
path_lbl = new Label { Text = "", Top = 12, Left = 16 + nextButton.Right };
panel.Height = 16 + openButton.Height;
panel.Controls.AddRange(new Control[] { openButton, prevButton, nextButton, path_lbl });
pageViewer = new PictureBox { Dock = DockStyle.Fill, SizeMode = PictureBoxSizeMode.Zoom };
ClientSize = new Size(850, 1100 + panel.Height);
Controls.AddRange(new Control[] { panel, pageViewer });
openButton.Click += OnOpenButtonClick;
prevButton.Click += OnPrevButtonClick;
nextButton.Click += OnNextButtonClick;
Disposed += OnFormDisposed;
UpdatePageInfo();
}
private Button openButton;
private Button prevButton;
private Button nextButton;
private PictureBox pageViewer;
private PageBuffer pageData;
private int currentPage;
private Size pageSize;
public string path;
private Label path_lbl;
private void OnOpenButtonClick(object sender, EventArgs e)
{
using (var dialog = new OpenFileDialog())
{
if (dialog.ShowDialog(this) == DialogResult.OK)
Open(dialog.FileName);
path = dialog.FileName;
}
}
private void OnPrevButtonClick(object sender, EventArgs e)
{
SelectPage(currentPage - 1);
}
private void OnNextButtonClick(object sender, EventArgs e)
{
//var data = PageBuffer.Open(path,Size= new Size(850,1150));
SelectPage(currentPage + 1);
//Debug.WriteLine("Current Size: 1300, 800");
}
private void OnFormDisposed(object sender, EventArgs e)
{
if (pageData != null)
pageData.Dispose();
}
private void Open(string path)
{
var data = PageBuffer.Open(path, new Size(1500, 1500));
pageViewer.Image = null;
if (pageData != null)
pageData.Dispose();
pageData = data;
SelectPage(0);
}
private void SelectPage(int index)
{
pageViewer.Image = pageData.GetPage(index);
currentPage = index;
UpdatePageInfo();
}
private void UpdatePageInfo()
{
prevButton.Enabled = pageData != null && currentPage > 0;
nextButton.Enabled = pageData != null && currentPage < pageData.PageCount - 1;
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new TestForm());
}
}
class PageBuffer : IDisposable
{
public const int DefaultCacheSize = 12; //This is how much images it will have in memory
public static PageBuffer Open(string path, Size maxSize, int cacheSize = DefaultCacheSize)
{
return new PageBuffer(File.OpenRead(path), maxSize, cacheSize);
}
private PageBuffer(Stream stream, Size maxSize, int cacheSize)
{
this.stream = stream;
source = Image.FromStream(stream);
pageCount = source.GetFrameCount(FrameDimension.Page);
if (pageCount < 2) return;
pageCache = new Image[Math.Min(pageCount, Math.Max(cacheSize, 5))];
pageSize = source.Size;
if (!maxSize.IsEmpty)
{
float scale = Math.Min((float)maxSize.Width / pageSize.Width, (float)maxSize.Height / pageSize.Height);
pageSize = new Size((int)(pageSize.Width * scale), (int)(pageSize.Height * scale));
}
var worker = new Thread(LoadPages) { IsBackground = true };
worker.Start();
}
private void LoadPages()
{
while (true)
{
lock (syncLock)
{
if (disposed) return;
int index = Array.FindIndex(pageCache, 0, pageCacheSize, p => p == null);
if (index < 0)
Monitor.Wait(syncLock);
else
pageCache[index] = LoadPage(pageCacheStart + index);
}
}
}
private Image LoadPage(int index)
{
source.SelectActiveFrame(FrameDimension.Page, index);
return new Bitmap(source, pageSize);
}
private Stream stream;
private Image source;
private int pageCount;
private Image[] pageCache;
private int pageCacheStart, pageCacheSize;
private object syncLock = new object();
private bool disposed;
private Size pageSize;
public Image Source { get { return source; } }
public int PageCount { get { return pageCount; } }
public Image GetPage(int index)
{
if (disposed) throw new ObjectDisposedException(GetType().Name);
if (PageCount < 2) return Source;
lock (syncLock)
{
AdjustPageCache(index);
int cacheIndex = index - pageCacheStart;
var image = pageCache[cacheIndex];
if (image == null)
image = pageCache[cacheIndex] = LoadPage(index);
return image;
}
}
private void AdjustPageCache(int pageIndex)
{
int start, end;
if ((start = pageIndex - pageCache.Length / 2) <= 0)
end = (start = 0) + pageCache.Length;
else if ((end = start + pageCache.Length) >= PageCount)
start = (end = PageCount) - pageCache.Length;
if (start < pageCacheStart)
{
int shift = pageCacheStart - start;
if (shift >= pageCacheSize)
ClearPageCache(0, pageCacheSize);
else
{
ClearPageCache(pageCacheSize - shift, pageCacheSize);
for (int j = pageCacheSize - 1, i = j - shift; i >= 0; j--, i--)
Exchange(ref pageCache[i], ref pageCache[j]);
}
}
else if (start > pageCacheStart)
{
int shift = start - pageCacheStart;
if (shift >= pageCacheSize)
ClearPageCache(0, pageCacheSize);
else
{
ClearPageCache(0, shift);
for (int j = 0, i = shift; i < pageCacheSize; j++, i++)
Exchange(ref pageCache[i], ref pageCache[j]);
}
}
if (pageCacheStart != start || pageCacheStart + pageCacheSize != end)
{
pageCacheStart = start;
pageCacheSize = end - start;
Monitor.Pulse(syncLock);
}
}
void ClearPageCache(int start, int end)
{
for (int i = start; i < end; i++)
Dispose(ref pageCache[i]);
}
static void Dispose<T>(ref T target) where T : class, IDisposable
{
var value = target;
if (value != null) value.Dispose();
target = null;
}
static void Exchange<T>(ref T a, ref T b) { var c = a; a = b; b = c; }
public void Dispose()
{
if (disposed) return;
lock (syncLock)
{
disposed = true;
if (pageCache != null)
{
ClearPageCache(0, pageCacheSize);
pageCache = null;
}
Dispose(ref source);
Dispose(ref stream);
if (pageCount > 2)
Monitor.Pulse(syncLock);
}
}
}
}

System.Console as a log window with input line

I'm writing a console application that needs user input on the bottom line while text is scrolling. The idea is to have text scroll and leave an input line at the bottom. I want text editing functionality (arrow-keys, insert, delete, etc). I'd love to be able to have static "status lines" too (lines unaffected by scrolling).
A real world example would be Irssi:
In my code I'm hooking up to NLog and writing its output to screen while also providing an input line to the user. It is done by "pausing input" on write: using Console.MoveBufferArea to move text up, disabling cursor, repositioning cursor, writing log-text, repositioning cursor back to input line and enabling cursor. It almost works, but there are some problems:
It is very slow. In cases where I write 20-30 lines the application slows down considerably. (Can be solved with buffering incoming, but won't solve scroll speed.)
Lines that overflow (i.e. exception stacktrace) leaves a line at the very bottom of the screen.
Lines that overflow are (partially) overwritten as text scrolls up. This also messes up input line.
Scrolling up/down does not work.
Is there a library to help me do this?
If not then how do I fix speed? How do I fix scrolling?
Cross platform solution preferred.
public class InputConsole
{
private static readonly object _bufferLock = new object();
private static int _windowWidth = Console.BufferWidth;
private static int _windowHeight = Console.BufferHeight;
private static int _windowLeft = Console.WindowLeft;
private static int _windowTop = Console.WindowTop;
public InputConsole()
{
MethodCallTarget target = new MethodCallTarget();
target.ClassName = typeof(InputConsole).AssemblyQualifiedName;
target.MethodName = "LogMethod";
target.Parameters.Add(new MethodCallParameter("${level}"));
target.Parameters.Add(new MethodCallParameter("${message}"));
target.Parameters.Add(new MethodCallParameter("${exception:format=tostring}"));
target.Parameters.Add(new MethodCallParameter("[${logger:shortName=true}]"));
SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Trace);
try
{
Console.SetWindowSize(180, 50);
Console.SetBufferSize(180, 50);
_windowWidth = Console.BufferWidth;
_windowHeight = Console.BufferHeight;
}
catch (Exception exception)
{
Console.WriteLine("Unable to resize console: " + exception);
}
}
public void Run()
{
string input;
do
{
lock (_bufferLock)
{
Console.SetCursorPosition(0, _windowHeight - 1);
Console.Write("Command: ");
Console.CursorVisible = true;
}
Console.BackgroundColor = ConsoleColor.Black;
Console.ForegroundColor = ConsoleColor.Yellow;
input = Console.ReadLine();
lock (_bufferLock)
{
Console.CursorVisible = false;
}
} while (!string.Equals(input, "quit", StringComparison.OrdinalIgnoreCase));
}
public static void LogMethod(string level, string message, string exception, string caller)
{
if (Console.BufferHeight == _windowHeight)
Console.MoveBufferArea(_windowLeft, _windowTop + 1, Console.BufferWidth, Console.BufferHeight - 2, _windowLeft, _windowTop);
var fgColor = ConsoleColor.White;
var bgColor = ConsoleColor.Black;
switch (level.ToUpper())
{
case "TRACE":
fgColor = ConsoleColor.DarkGray;
break;
case "DEBUG":
fgColor = ConsoleColor.Gray;
break;
case "INFO":
fgColor = ConsoleColor.White;
break;
case "WARNING":
fgColor = ConsoleColor.Cyan;
break;
case "ERROR":
fgColor = ConsoleColor.White;
bgColor = ConsoleColor.Red;
break;
}
var str = string.Format("({0}) {1} {2} {3}", level.ToUpper(), caller, message, exception);
WriteAt(_windowLeft, _windowHeight - 3, str, fgColor, bgColor);
}
public static void WriteAt(int left, int top, string s, ConsoleColor foregroundColor = ConsoleColor.White, ConsoleColor backgroundColor = ConsoleColor.Black)
{
lock (_bufferLock)
{
var currentBackgroundColor = Console.BackgroundColor;
var currentForegroundColor = Console.ForegroundColor;
Console.BackgroundColor = backgroundColor;
Console.ForegroundColor = foregroundColor;
int currentLeft = Console.CursorLeft;
int currentTop = Console.CursorTop;
var currentVisible = Console.CursorVisible;
Console.CursorVisible = false;
Console.SetCursorPosition(left, top);
Console.Write(s);
Console.SetCursorPosition(currentLeft, currentTop);
Console.CursorVisible = currentVisible;
Console.BackgroundColor = currentBackgroundColor;
Console.ForegroundColor = currentForegroundColor;
}
}
}
Doing further research into text console in Windows I seems it is difficult to make it go faster. Through a custom implementation with lower redraw rates (less to WriteConsoleOutput) I was able to get just over 10x speed increase over Console.WriteLine.
However since Console.WriteLine enforces the "scroll everything when we reach bottom" I was using Console.MoveBufferArea. Tests shows that my implementation of MoveBufferArea (included in my original question) was around 90x slower than Console.WriteLine. With my new implementation using WriteConsoleOutput I was however able to get a 1356x speed increase over MoveBufferedArea.
Since it was a bit difficult to find information about it I have detailed my finding in a blog post. I'm also attaching the code to this answer for posterity.
I have written a class that allows me to scroll individual boxes. I have also implemented a line input system to emulate that of standard Console.ReadLine();. Note that this implementation is missing home/end-support (easy to fix though).
Note that to get any speed increase from it you have to set box.AutoRedraw = false; and manually call box.Draw(); regularly. With box.AutoRedraw = true; (calling Draw() on every Write()) this solution is actually 30 times slower than Console.WriteLine and 3 times faster than MoveBufferArea.
Example on how to use:
_logBox = new InputConsoleBox(0, 0, (short)Console.BufferWidth, (short)(Console.BufferHeight - 2), InputConsoleBox.Colors.LightWhite, InputConsoleBox.Colors.Black);
_statusBox = new InputConsoleBox(0, (short)(Console.BufferHeight - 3), (short)Console.BufferWidth, 1, InputConsoleBox.Colors.LightYellow, InputConsoleBox.Colors.DarkBlue);
_inputBox = new InputConsoleBox(0, (short)(Console.BufferHeight - 2), (short)Console.BufferWidth, 1, InputConsoleBox.Colors.LightYellow, InputConsoleBox.Colors.Black);
_statusBox.WriteLine("Hey there!");
_inputBox.InputPrompt = "Command: ";
// If you are okay with some slight flickering this is an easy way to set up a refresh timer
_logBox.AutoDraw = false;
_redrawTask = Task.Factory.StartNew(async () =>
{
while (true)
{
await Task.Delay(100);
if (_logBox.IsDirty)
_logBox.Draw();
}
});
// Line input box
var line = _inputBox.ReadLine(); // Blocking while waiting for <enter>
Code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Win32.SafeHandles;
public class InputConsoleBox
{
#region Output
#region Win32 interop
private const UInt32 STD_OUTPUT_HANDLE = unchecked((UInt32)(-11));
private const UInt32 STD_ERROR_HANDLE = unchecked((UInt32)(-12));
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr GetStdHandle(UInt32 type);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern SafeFileHandle CreateFile(
string fileName,
[MarshalAs(UnmanagedType.U4)] uint fileAccess,
[MarshalAs(UnmanagedType.U4)] uint fileShare,
IntPtr securityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] int flags,
IntPtr template);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteConsoleOutput(
SafeFileHandle hConsoleOutput,
CharInfo[] lpBuffer,
Coord dwBufferSize,
Coord dwBufferCoord,
ref SmallRect lpWriteRegion);
[StructLayout(LayoutKind.Sequential)]
private struct Coord
{
public short X;
public short Y;
public Coord(short X, short Y)
{
this.X = X;
this.Y = Y;
}
};
[StructLayout(LayoutKind.Explicit)]
private struct CharUnion
{
[FieldOffset(0)]
public char UnicodeChar;
[FieldOffset(0)]
public byte AsciiChar;
}
[StructLayout(LayoutKind.Explicit)]
private struct CharInfo
{
[FieldOffset(0)]
public CharUnion Char;
[FieldOffset(2)]
public ushort Attributes;
public CharInfo(char #char, ushort attributes)
{
this.Char = new CharUnion();
Char.UnicodeChar = #char;
Attributes = attributes;
}
}
[StructLayout(LayoutKind.Sequential)]
private struct SmallRect
{
public short Left;
public short Top;
public short Right;
public short Bottom;
}
#endregion
#region Colors Enum
private const int HighIntensity = 0x0008;
private const ushort COMMON_LVB_LEADING_BYTE = 0x0100;
private const ushort COMMON_LVB_TRAILING_BYTE = 0x0200;
private const ushort COMMON_LVB_GRID_HORIZONTAL = 0x0400;
private const ushort COMMON_LVB_GRID_LVERTICAL = 0x0800;
private const ushort COMMON_LVB_GRID_RVERTICAL = 0x1000;
private const ushort COMMON_LVB_REVERSE_VIDEO = 0x4000;
private const ushort COMMON_LVB_UNDERSCORE = 0x8000;
private const ushort COMMON_LVB_SBCSDBCS = 0x0300;
[Flags]
public enum Colors : int
{
Black = 0x0000,
DarkBlue = 0x0001,
DarkGreen = 0x0002,
DarkRed = 0x0004,
Gray = DarkBlue | DarkGreen | DarkRed,
DarkYellow = DarkRed | DarkGreen,
DarkPurple = DarkRed | DarkBlue,
DarkCyan = DarkGreen | DarkBlue,
LightBlue = DarkBlue | HighIntensity,
LightGreen = DarkGreen | HighIntensity,
LightRed = DarkRed | HighIntensity,
LightWhite = Gray | HighIntensity,
LightYellow = DarkYellow | HighIntensity,
LightPurple = DarkPurple | HighIntensity,
LightCyan = DarkCyan | HighIntensity
}
#endregion // Colors Enum
private readonly CharInfo[] _buffer;
private readonly List<CharInfo> _tmpBuffer;
private readonly short _left;
private readonly short _top;
private readonly short _width;
private readonly short _height;
private ushort _defaultColor;
private int _cursorLeft;
private int _cursorTop;
private static SafeFileHandle _safeFileHandle;
/// <summary>
/// Automatically draw to console.
/// Unset this if you want to manually control when (and what order) boxes are writen to consoles - or you want to batch some stuff.
/// You must manually call <c>Draw()</c> to write to console.
/// </summary>
public bool AutoDraw = true;
public bool IsDirty { get; private set; }
public InputConsoleBox(short left, short top, short width, short height, Colors defaultForegroundColor = Colors.Gray, Colors defaultBackgroundColor = Colors.Black)
{
if (left < 0 || top < 0 || left + width > Console.BufferWidth || top + height > Console.BufferHeight)
throw new Exception(string.Format("Attempting to create a box {0},{1}->{2},{3} that is out of buffer bounds 0,0->{4},{5}", left, top, left + width, top + height, Console.BufferWidth, Console.BufferHeight));
_left = left;
_top = top;
_width = width;
_height = height;
_buffer = new CharInfo[_width * _height];
_defaultColor = CombineColors(defaultForegroundColor, defaultBackgroundColor);
_tmpBuffer = new List<CharInfo>(_width * _height); // Assumption that we won't be writing much more than a screenful (backbufferfull) in every write operation
//SafeFileHandle h = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
if (_safeFileHandle == null)
{
var stdOutputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
_safeFileHandle = new SafeFileHandle(stdOutputHandle, false);
}
Clear();
Draw();
}
public void Clear()
{
for (int y = 0; y < _height; y++)
{
for (int x = 0; x < _width; x++)
{
var i = (y * _width) + x;
_buffer[i].Char.UnicodeChar = ' ';
_buffer[i].Attributes = _defaultColor;
}
}
IsDirty = true;
// Update screen
if (AutoDraw)
Draw();
}
public void Draw()
{
IsDirty = false;
var rect = new SmallRect() { Left = _left, Top = _top, Right = (short)(_left + _width), Bottom = (short)(_top + _height) };
bool b = WriteConsoleOutput(_safeFileHandle, _buffer,
new Coord(_width, _height),
new Coord(0, 0), ref rect);
}
private static ushort CombineColors(Colors foreColor, Colors backColor)
{
return (ushort)((int)foreColor + (((int)backColor) << 4));
}
public void SetCursorPosition(int left, int top)
{
if (left >= _width || top >= _height)
throw new Exception(string.Format("Position out of bounds attempting to set cursor at box pos {0},{1} when box size is only {2},{3}.", left, top, _width, _height));
_cursorLeft = left;
_cursorTop = top;
}
public void SetCursorBlink(int left, int top, bool state)
{
Console.SetCursorPosition(left, top);
Console.CursorVisible = state;
//// Does not work
//var i = (top * _width) + left;
//if (state)
// _buffer[i].Attributes = (ushort)((int)_buffer[i].Attributes & ~(int)COMMON_LVB_UNDERSCORE);
//else
// _buffer[i].Attributes = (ushort)((int)_buffer[i].Attributes | (int)COMMON_LVB_UNDERSCORE);
//if (AutoDraw)
// Draw();
}
public void WriteLine(string line, Colors fgColor, Colors bgColor)
{
var c = _defaultColor;
_defaultColor = CombineColors(fgColor, bgColor);
WriteLine(line);
_defaultColor = c;
}
public void WriteLine(string line)
{
Write(line + "\n");
}
public void Write(string text)
{
Write(text.ToCharArray());
}
public void Write(char[] text)
{
IsDirty = true;
_tmpBuffer.Clear();
bool newLine = false;
// Old-school! Could definitively have been done more easily with regex. :)
var col = 0;
var row = -1;
for (int i = 0; i < text.Length; i++)
{
// Detect newline
if (text[i] == '\n')
newLine = true;
if (text[i] == '\r')
{
newLine = true;
// Skip following \n
if (i + 1 < text.Length && text[i] == '\n')
i++;
}
// Keep track of column and row
col++;
if (col == _width)
{
col = 0;
row++;
if (newLine) // Last character was newline? Skip filling the whole next line with empty
{
newLine = false;
continue;
}
}
// If we are newlining we need to fill the remaining with blanks
if (newLine)
{
newLine = false;
for (int i2 = col; i2 <= _width; i2++)
{
_tmpBuffer.Add(new CharInfo(' ', _defaultColor));
}
col = 0;
row++;
continue;
}
if (i >= text.Length)
break;
// Add character
_tmpBuffer.Add(new CharInfo(text[i], _defaultColor));
}
var cursorI = (_cursorTop * _width) + _cursorLeft;
// Get our end position
var end = cursorI + _tmpBuffer.Count;
// If we are overflowing (scrolling) then we need to complete our last line with spaces (align buffer with line ending)
if (end > _buffer.Length && col != 0)
{
for (int i = col; i <= _width; i++)
{
_tmpBuffer.Add(new CharInfo(' ', _defaultColor));
}
col = 0;
row++;
}
// Chop start of buffer to fit into destination buffer
if (_tmpBuffer.Count > _buffer.Length)
_tmpBuffer.RemoveRange(0, _tmpBuffer.Count - _buffer.Length);
// Convert to array so we can batch copy
var tmpArray = _tmpBuffer.ToArray();
// Are we going to write outside of buffer?
end = cursorI + _tmpBuffer.Count;
var scrollUp = 0;
if (end > _buffer.Length)
{
scrollUp = end - _buffer.Length;
}
// Scroll up
if (scrollUp > 0)
{
Array.Copy(_buffer, scrollUp, _buffer, 0, _buffer.Length - scrollUp);
cursorI -= scrollUp;
}
var lastPos = Math.Min(_buffer.Length, cursorI + tmpArray.Length);
var firstPos = lastPos - tmpArray.Length;
// Copy new data in
Array.Copy(tmpArray, 0, _buffer, firstPos, tmpArray.Length);
// Set new cursor position
_cursorLeft = col;
_cursorTop = Math.Min(_height, _cursorTop + row + 1);
// Write to main buffer
if (AutoDraw)
Draw();
}
#endregion
#region Input
private string _currentInputBuffer = "";
private string _inputPrompt;
private int _inputCursorPos = 0;
private int _inputFrameStart = 0;
// Not used because COMMON_LVB_UNDERSCORE doesn't work
//private bool _inputCursorState = false;
//private int _inputCursorStateChange = 0;
private int _cursorBlinkLeft = 0;
private int _cursorBlinkTop = 0;
public string InputPrompt
{
get { return _inputPrompt; }
set
{
_inputPrompt = value;
ResetInput();
}
}
private void ResetInput()
{
SetCursorPosition(0, 0);
_inputCursorPos = Math.Min(_currentInputBuffer.Length, _inputCursorPos);
var inputPrompt = InputPrompt + "[" + _currentInputBuffer.Length + "] ";
// What is the max length we can write?
var maxLen = _width - inputPrompt.Length;
if (maxLen < 0)
return;
if (_inputCursorPos > _inputFrameStart + maxLen)
_inputFrameStart = _inputCursorPos - maxLen;
if (_inputCursorPos < _inputFrameStart)
_inputFrameStart = _inputCursorPos;
_cursorBlinkLeft = inputPrompt.Length + _inputCursorPos - _inputFrameStart;
//if (_currentInputBuffer.Length - _inputFrameStart < maxLen)
// _inputFrameStart--;
// Write and pad the end
var str = inputPrompt + _currentInputBuffer.Substring(_inputFrameStart, Math.Min(_currentInputBuffer.Length - _inputFrameStart, maxLen));
var spaceLen = _width - str.Length;
Write(str + (spaceLen > 0 ? new String(' ', spaceLen) : ""));
UpdateCursorBlink(true);
}
private void UpdateCursorBlink(bool force)
{
// Since COMMON_LVB_UNDERSCORE doesn't work we won't be controlling blink
//// Blink the cursor
//if (Environment.TickCount > _inputCursorStateChange)
//{
// _inputCursorStateChange = Environment.TickCount + 250;
// _inputCursorState = !_inputCursorState;
// force = true;
//}
//if (force)
// SetCursorBlink(_cursorBlinkLeft, _cursorBlinkTop, _inputCursorState);
SetCursorBlink(_left + _cursorBlinkLeft, _top + _cursorBlinkTop, true);
}
public string ReadLine()
{
Console.CursorVisible = false;
Clear();
ResetInput();
while (true)
{
Thread.Sleep(50);
while (Console.KeyAvailable)
{
var key = Console.ReadKey(true);
switch (key.Key)
{
case ConsoleKey.Enter:
{
var ret = _currentInputBuffer;
_inputCursorPos = 0;
_currentInputBuffer = "";
return ret;
break;
}
case ConsoleKey.LeftArrow:
{
_inputCursorPos = Math.Max(0, _inputCursorPos - 1);
break;
}
case ConsoleKey.RightArrow:
{
_inputCursorPos = Math.Min(_currentInputBuffer.Length, _inputCursorPos + 1);
break;
}
case ConsoleKey.Backspace:
{
if (_inputCursorPos > 0)
{
_inputCursorPos--;
_currentInputBuffer = _currentInputBuffer.Remove(_inputCursorPos, 1);
}
break;
}
case ConsoleKey.Delete:
{
if (_inputCursorPos < _currentInputBuffer.Length - 1)
_currentInputBuffer = _currentInputBuffer.Remove(_inputCursorPos, 1);
break;
}
default:
{
var pos = _inputCursorPos;
//if (_inputCursorPos == _currentInputBuffer.Length)
_inputCursorPos++;
_currentInputBuffer = _currentInputBuffer.Insert(pos, key.KeyChar.ToString());
break;
}
}
ResetInput();
}
// COMMON_LVB_UNDERSCORE doesn't work so we use Consoles default cursor
//UpdateCursorBlink(false);
}
}
#endregion
}

WrapPanel with last-in-row fill

What I need: listbox with textboxes inside, textboxes wraps, and last in row fills remaining space:
|word 1||word 2___|
|word 3___________|
I'm trying to implement this behaviour using that advice. My xaml:
<ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Tags}"
HorizontalContentAlignment="Stretch">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanelLastChildFill />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Text}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
MyWrapPanel (inherits form WrapPanel) code:
protected override Size MeasureOverride(Size constraint)
{
Size curLineSize = new Size();
Size panelSize = new Size(constraint.Width, 0);
UIElementCollection children = base.InternalChildren;
for (int i = 0; i < children.Count; i++)
{
UIElement child = children[i] as UIElement;
child.Measure(constraint);
Size sz = child.DesiredSize;
if (curLineSize.Width + sz.Width > constraint.Width) // new line
{
panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
panelSize.Height += curLineSize.Height;
if (i > 0)
{
// change width of prev control here
var lastChildInRow = children[i - 1] as Control;
lastChildInRow.Width = lastChildInRow.ActualWidth + panelSize.Width - curLineSize.Width;
}
curLineSize = sz;
}
else
{
curLineSize.Width += sz.Width;
curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
}
}
panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
panelSize.Height += curLineSize.Height;
return panelSize;
}
Thats work, but in one side only - textbox width never shrinks.
Any help appreciated.
I have done it recently.
You can see my code at: CodeProject
or use this class directly as shown:
Usage:
<TextBox MinWidth="120" wrapPanelWithFill:WrapPanelFill.UseToFill="True">*</TextBox>
Code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
namespace WrapPanelWithFill
{
public class WrapPanelFill : WrapPanel
{
// ******************************************************************
public static readonly DependencyProperty UseToFillProperty = DependencyProperty.RegisterAttached("UseToFill", typeof(Boolean),
typeof(WrapPanelFill), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
// ******************************************************************
public static void SetUseToFill(UIElement element, Boolean value)
{
element.SetValue(UseToFillProperty, value);
}
// ******************************************************************
public static Boolean GetUseToFill(UIElement element)
{
return (Boolean)element.GetValue(UseToFillProperty);
}
// ******************************************************************
const double DBL_EPSILON = 2.2204460492503131e-016; /* smallest such that 1.0+DBL_EPSILON != 1.0 */
// ******************************************************************
private static bool DoubleAreClose(double value1, double value2)
{
//in case they are Infinities (then epsilon check does not work)
if (value1 == value2) return true;
// This computes (|value1-value2| / (|value1| + |value2| + 10.0)) < DBL_EPSILON
double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * DBL_EPSILON;
double delta = value1 - value2;
return (-eps < delta) && (eps > delta);
}
// ******************************************************************
private static bool DoubleGreaterThan(double value1, double value2)
{
return (value1 > value2) && !DoubleAreClose(value1, value2);
}
// ******************************************************************
private bool _atLeastOneElementCanHasItsWidthExpanded = false;
// ******************************************************************
/// <summary>
/// <see cref="FrameworkElement.MeasureOverride"/>
/// </summary>
protected override Size MeasureOverride(Size constraint)
{
UVSize curLineSize = new UVSize(Orientation);
UVSize panelSize = new UVSize(Orientation);
UVSize uvConstraint = new UVSize(Orientation, constraint.Width, constraint.Height);
double itemWidth = ItemWidth;
double itemHeight = ItemHeight;
bool itemWidthSet = !Double.IsNaN(itemWidth);
bool itemHeightSet = !Double.IsNaN(itemHeight);
Size childConstraint = new Size(
(itemWidthSet ? itemWidth : constraint.Width),
(itemHeightSet ? itemHeight : constraint.Height));
UIElementCollection children = InternalChildren;
// EO
LineInfo currentLineInfo = new LineInfo(); // EO, the way it works it is always like we are on the current line
_lineInfos.Clear();
_atLeastOneElementCanHasItsWidthExpanded = false;
for (int i = 0, count = children.Count; i < count; i++)
{
UIElement child = children[i] as UIElement;
if (child == null) continue;
//Flow passes its own constrint to children
child.Measure(childConstraint);
//this is the size of the child in UV space
UVSize sz = new UVSize(
Orientation,
(itemWidthSet ? itemWidth : child.DesiredSize.Width),
(itemHeightSet ? itemHeight : child.DesiredSize.Height));
if (DoubleGreaterThan(curLineSize.U + sz.U, uvConstraint.U)) //need to switch to another line
{
// EO
currentLineInfo.Size = curLineSize;
_lineInfos.Add(currentLineInfo);
panelSize.U = Math.Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
curLineSize = sz;
// EO
currentLineInfo = new LineInfo();
var feChild = child as FrameworkElement;
if (GetUseToFill(feChild))
{
currentLineInfo.ElementsWithNoWidthSet.Add(feChild);
_atLeastOneElementCanHasItsWidthExpanded = true;
}
if (DoubleGreaterThan(sz.U, uvConstraint.U)) //the element is wider then the constrint - give it a separate line
{
currentLineInfo = new LineInfo();
panelSize.U = Math.Max(sz.U, panelSize.U);
panelSize.V += sz.V;
curLineSize = new UVSize(Orientation);
}
}
else //continue to accumulate a line
{
curLineSize.U += sz.U;
curLineSize.V = Math.Max(sz.V, curLineSize.V);
// EO
var feChild = child as FrameworkElement;
if (GetUseToFill(feChild))
{
currentLineInfo.ElementsWithNoWidthSet.Add(feChild);
_atLeastOneElementCanHasItsWidthExpanded = true;
}
}
}
if (curLineSize.U > 0)
{
currentLineInfo.Size = curLineSize;
_lineInfos.Add(currentLineInfo);
}
//the last line size, if any should be added
panelSize.U = Math.Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
// EO
if (_atLeastOneElementCanHasItsWidthExpanded)
{
return new Size(constraint.Width, panelSize.Height);
}
//go from UV space to W/H space
return new Size(panelSize.Width, panelSize.Height);
}
// ************************************************************************
private struct UVSize
{
internal UVSize(Orientation orientation, double width, double height)
{
U = V = 0d;
_orientation = orientation;
Width = width;
Height = height;
}
internal UVSize(Orientation orientation)
{
U = V = 0d;
_orientation = orientation;
}
internal double U;
internal double V;
private Orientation _orientation;
internal double Width
{
get { return (_orientation == Orientation.Horizontal ? U : V); }
set { if (_orientation == Orientation.Horizontal) U = value; else V = value; }
}
internal double Height
{
get { return (_orientation == Orientation.Horizontal ? V : U); }
set { if (_orientation == Orientation.Horizontal) V = value; else U = value; }
}
}
// ************************************************************************
private class LineInfo
{
public List<UIElement> ElementsWithNoWidthSet = new List<UIElement>();
// public double SpaceLeft = 0;
// public double WidthCorrectionPerElement = 0;
public UVSize Size;
public double Correction = 0;
}
private List<LineInfo> _lineInfos = new List<LineInfo>();
// ************************************************************************
/// <summary>
/// <see cref="FrameworkElement.ArrangeOverride"/>
/// </summary>
protected override Size ArrangeOverride(Size finalSize)
{
int lineIndex = 0;
int firstInLine = 0;
double itemWidth = ItemWidth;
double itemHeight = ItemHeight;
double accumulatedV = 0;
double itemU = (Orientation == Orientation.Horizontal ? itemWidth : itemHeight);
UVSize curLineSize = new UVSize(Orientation);
UVSize uvFinalSize = new UVSize(Orientation, finalSize.Width, finalSize.Height);
bool itemWidthSet = !Double.IsNaN(itemWidth);
bool itemHeightSet = !Double.IsNaN(itemHeight);
bool useItemU = (Orientation == Orientation.Horizontal ? itemWidthSet : itemHeightSet);
UIElementCollection children = InternalChildren;
for (int i = 0, count = children.Count; i < count; i++)
{
UIElement child = children[i] as UIElement;
if (child == null) continue;
UVSize sz = new UVSize(
Orientation,
(itemWidthSet ? itemWidth : child.DesiredSize.Width),
(itemHeightSet ? itemHeight : child.DesiredSize.Height));
if (DoubleGreaterThan(curLineSize.U + sz.U, uvFinalSize.U)) //need to switch to another line
{
arrangeLine(lineIndex, accumulatedV, curLineSize.V, firstInLine, i, useItemU, itemU, uvFinalSize);
lineIndex++;
accumulatedV += curLineSize.V;
curLineSize = sz;
if (DoubleGreaterThan(sz.U, uvFinalSize.U)) //the element is wider then the constraint - give it a separate line
{
//switch to next line which only contain one element
arrangeLine(lineIndex, accumulatedV, sz.V, i, ++i, useItemU, itemU, uvFinalSize);
accumulatedV += sz.V;
curLineSize = new UVSize(Orientation);
}
firstInLine = i;
}
else //continue to accumulate a line
{
curLineSize.U += sz.U;
curLineSize.V = Math.Max(sz.V, curLineSize.V);
}
}
//arrange the last line, if any
if (firstInLine < children.Count)
{
arrangeLine(lineIndex, accumulatedV, curLineSize.V, firstInLine, children.Count, useItemU, itemU, uvFinalSize);
}
return finalSize;
}
// ************************************************************************
private void arrangeLine(int lineIndex, double v, double lineV, int start, int end, bool useItemU, double itemU, UVSize uvFinalSize)
{
double u = 0;
bool isHorizontal = (Orientation == Orientation.Horizontal);
Debug.Assert(lineIndex < _lineInfos.Count);
LineInfo lineInfo = _lineInfos[lineIndex];
double lineSpaceAvailableForCorrection = Math.Max(uvFinalSize.U - lineInfo.Size.U, 0);
double perControlCorrection = 0;
if (lineSpaceAvailableForCorrection > 0 && lineInfo.Size.U > 0)
{
perControlCorrection = lineSpaceAvailableForCorrection / lineInfo.ElementsWithNoWidthSet.Count;
if (double.IsInfinity(perControlCorrection))
{
perControlCorrection = 0;
}
}
int indexOfControlToAdjustSizeToFill = 0;
UIElement uIElementToAdjustNext = indexOfControlToAdjustSizeToFill < lineInfo.ElementsWithNoWidthSet.Count ? lineInfo.ElementsWithNoWidthSet[indexOfControlToAdjustSizeToFill] : null;
UIElementCollection children = InternalChildren;
for (int i = start; i < end; i++)
{
UIElement child = children[i] as UIElement;
if (child != null)
{
UVSize childSize = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
double layoutSlotU = (useItemU ? itemU : childSize.U);
if (perControlCorrection > 0 && child == uIElementToAdjustNext)
{
layoutSlotU += perControlCorrection;
indexOfControlToAdjustSizeToFill++;
uIElementToAdjustNext = indexOfControlToAdjustSizeToFill < lineInfo.ElementsWithNoWidthSet.Count ? lineInfo.ElementsWithNoWidthSet[indexOfControlToAdjustSizeToFill] : null;
}
child.Arrange(new Rect(
(isHorizontal ? u : v),
(isHorizontal ? v : u),
(isHorizontal ? layoutSlotU : lineV),
(isHorizontal ? lineV : layoutSlotU)));
u += layoutSlotU;
}
}
}
// ************************************************************************
}
}
It was misunderstanding where to place width correction. This must be in ArrangeOverride:
private void ArrangeLine(double y, Size lineSize, double boundsWidth, int start, int end)
{
double x = 0;
UIElementCollection children = InternalChildren;
for (int i = start; i < end; i++)
{
UIElement child = children[i];
var w = child.DesiredSize.Width;
if (LastChildFill && i == end - 1) // last сhild fills remaining space
{
w = boundsWidth - x;
}
child.Arrange(new Rect(x, y, w, lineSize.Height));
x += w;
}
}

How to create a usercontrol on different thread?

I am creating a UI that will be displaying generated patterns similar to fractals and cellular automation, but they will be continuously generating and automated.
The pixels and pixel colors will be displayed as a grid of squares in a usercontrol. I've already created the usercontrol to display this but since it is constantly calculation at every timer.tick it dramatically slows down the rest of the UI and causes all other elements to stutter.
So I looked into threading and set the "calculating" part in a BackgroundWorker DoWork(), which ultimately didn't end up working out the way I wanted it to. The BackgroundWorker is using data from the main thread (Rectangle[400]), so I had to use Dispatcher.Invoke(new Action(() => { })); which simply defeats the purpose, the program still ran very "stuttery".
So, how can I create a usercontrol...name:display_control entirely on a separate thread and have it appear in another usercontrol...name:unsercontrol1 (Which is running in the main thread), ? Then I could possibly databind the user_input data with the usercontrol1 User_Input_Class instance.
Or, is there a better way to achieve this? Only other way I can think of doing this is to simply create an entirely separate program for the display and share a file containing the user_input data which is very unprofessional.
public partial class Fractal_Gen_A : UserControl
{
byte W_R = 0;
byte W_G = 255;
byte W_B = 0;
int Pixel_Max_Width = 20;
int Pixel_Max_Height = 20;
Color[] Pixel_Color = new Color[20 * 20]; //Width_Max * Canvas_Height_Count
Rectangle[] Pixel = new Rectangle[20 * 20];
Color[] Temp_Color = new Color[20 * 20];
BackgroundWorker worker = new BackgroundWorker();
private void Timer_Tick(object sender, EventArgs e)
{
try { worker.RunWorkerAsync(); }
catch {}
}
Function_Classes.Main_Binder.FGA LB = new Function_Classes.Main_Binder.FGA(); //LB = local Binder
DispatcherTimer Timer = new DispatcherTimer();
public Fractal_Gen_A()
{
LB.Brush = new SolidColorBrush[Pixel_Max_Width * Pixel_Max_Height];
InitializeComponent();
DataContext = LB;
Set_Up_Binded_Brushes();
worker.DoWork += Worker_Work;
Timer.Tick += new EventHandler(Timer_Tick);
Timer.Interval = new TimeSpan(0, 0, 0, 0, 300);
}
private void Set_Up_Binded_Brushes()
{
for (int i = 0; i < Pixel_Max_Width *Pixel_Max_Height; i++)
{
LB.Brush[i] = new SolidColorBrush(Color.FromRgb(255, 0, 0));
}
}
private delegate void UpdateMyDelegatedelegate(int i);
private void UpdateMyDelegateLabel(int i){}
private void Worker_Work(object sender, DoWorkEventArgs e)
{
try
{
BackgroundWorker Worker = sender as BackgroundWorker;
SolidColorBrush[] Temp_Brush = new SolidColorBrush[Pixel_Max_Height * Pixel_Max_Width];
for (int h = 0; h < Pixel_Max_Height - 1; h++)
{
for (int w = 0; w < Pixel_Max_Width; w++)
{
Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() => {
Temp_Brush[((h + 1) * Pixel_Max_Width) + w] = new SolidColorBrush();
Temp_Brush[((h + 1) * Pixel_Max_Width) + w].Color = LB.Brush[(h * Pixel_Max_Width) + w].Color;
}));
}
}
W_R += 23;
for (int w = 0; w < Pixel_Max_Width; w++)
{
W_B += 23 % 255;
W_R += 23 % 255;
Temp_Brush[w].Color = Color.FromRgb(W_R, W_B, W_G);
}
Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() => {
Array.Copy(Temp_Brush, 0, LB.Brush, 0, Pixel_Max_Height * Pixel_Max_Width);
}));
UpdateMyDelegatedelegate UpdateMyDelegate = new UpdateMyDelegatedelegate(UpdateMyDelegateLabel);
}
catch { }
}
private void Worker_Done(object sender, RunWorkerCompletedEventArgs e)
{
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
double X_Set = Pixel_Canvas.ActualWidth / Pixel_Max_Width;
double Y_Set = Pixel_Canvas.ActualHeight / Pixel_Max_Height;
for (int h = 0; h < Pixel_Max_Height; h++)
{
for (int w = 0; w < Pixel_Max_Width; w++)
{
Pixel_Color[(h * Pixel_Max_Width) + w] = Color.FromRgb(255, 0, 0);
Pixel[(h * Pixel_Max_Width) + w] = new Rectangle();
Pixel[(h * Pixel_Max_Width) + w].Width = Pixel_Canvas.ActualWidth / Pixel_Max_Width;
Pixel[(h * Pixel_Max_Width) + w].Height = Pixel_Canvas.ActualHeight / Pixel_Max_Height;
Pixel[(h * Pixel_Max_Width) + w].Stroke = new SolidColorBrush(Color.FromRgb(100, 100, 100)); //Pixel_Color[(h * Pixel_Max_Width) + w]
Pixel[(h * Pixel_Max_Width) + w].StrokeThickness = .5;
Pixel[(h * Pixel_Max_Width) + w].Fill = new SolidColorBrush(Color.FromRgb(255, 0, 0)); //Pixel_Color[(h * Pixel_Max_Width) + w]
Canvas.SetLeft(Pixel[(h * Pixel_Max_Width) + w], (X_Set * w) + 0);
Canvas.SetTop(Pixel[(h * Pixel_Max_Height) + w], (Y_Set * h) + 0);
Pixel_Canvas.Children.Add(Pixel[(h * Pixel_Max_Height) + w]);
int Temp_Count = (h * Pixel_Max_Width) + w;
string Temp_Bind = "Brush[" + Temp_Count.ToString() + "]";
Binding Bind = new Binding(Temp_Bind);
Pixel[(h * Pixel_Max_Height) + w].SetBinding(Rectangle.FillProperty, Bind );
// Dispatcher.Invoke(new Action(() => { }));
Timer.Start();
}
}
Timer.Start();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Window pw = new PopUp_Window();
pw.Show();
}
}
Basically, I am using usercontrols to act as views, 2 will be displayed at all times, one on the left one on the right.
Ok. Delete all your code and start all over.
If you're working with WPF, you really need to embrace The WPF Mentality
This is how you do that in WPF:
<Window x:Class="MiscSamples.Fractals"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Fractals" Height="300" Width="300">
<ItemsControl ItemsSource="{Binding Cells}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding Size}" Columns="{Binding Size}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Gray" BorderThickness="2">
<Border.Background>
<SolidColorBrush Color="{Binding Color,Mode=OneTime}"/>
</Border.Background>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
Code Behind:
public partial class Fractals : Window
{
public Fractals()
{
InitializeComponent();
DataContext = new FractalViewModel();
}
}
ViewModel:
public class FractalViewModel:PropertyChangedBase
{
private ObservableCollection<FractalCell> _cells;
public int Rows { get; set; }
public int Columns { get; set; }
public ObservableCollection<FractalCell> Cells
{
get { return _cells; }
set
{
_cells = value;
OnPropertyChanged("Cells");
}
}
public FractalViewModel()
{
var ctx = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => CreateFractalCellsAsync())
.ContinueWith(x => Cells = new ObservableCollection<FractalCell>(x.Result), ctx);
}
private List<FractalCell> CreateFractalCellsAsync()
{
var cells = new List<FractalCell>();
var colors = typeof(Colors).GetProperties().Select(x => (Color)x.GetValue(null, null)).ToList();
var random = new Random();
for (int i = 0; i < 32; i++)
{
for (int j = 0; j < 32; j++)
{
cells.Add(new FractalCell() { Row = i, Column = j, Color = colors[random.Next(0, colors.Count)] });
}
}
return cells;
}
}
Data Item:
public class FractalCell:PropertyChangedBase
{
public int Row { get; set; }
public int Column { get; set; }
public Color Color { get; set; }
}
PropertyChangedBase class:
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Result:
Notice how I'm not manipulating any UI elements in procedural code. Everything is done with simple, simple Properties and INotifyPropertyChanged. That's how you program in WPF.
I'm using an ItemsControl with a UniformGrid and a simple DataTemplate for the cells.
The example is generating random colors, but you can get this from whatever data source you like. Notice that the data is completely decoupled from the UI and thus it makes it much easier for you to manipulate your own, simple classes rather than the complex and arcane WPF object model.
It also makes it easier for you to implement a multi-threaded scenario because, again, you are not dealing with the UI, but rather with Data. Remember that the UI can only be manipulated in the UI Thread.
WPF Rocks. Just copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.
Let me know if you need further help.

Categories