I am using TeeChart and the Histogram Series to display data.
I would like to color the bins individually depending on the values but all I found was the option to color each one differently. I want the bins displaying the same value to have the same color. Is that possible with TeeChart?
Yes, this is possible. You can achieve that either providing the color value when populating the series using the appropriate Add method override or using the BeforeDrawPoint event as shown here:
public Form1()
{
InitializeComponent();
InitializeChart();
}
private void InitializeChart()
{
tChart1.Aspect.View3D = false;
Histogram histogram1 = new Histogram(tChart1.Chart);
histogram1.LinePen.Visible = false;
histogram1.LinesPen.Visible = false;
for (int i = 0; i < 20; i++)
{
histogram1.Add(i);
}
histogram1.BeforeDrawPoint += histogram1_BeforeDrawPoint;
}
void histogram1_BeforeDrawPoint(Series series, BeforeDrawPointEventArgs e)
{
series.Colors[e.ValueIndex] = (series.YValues[e.ValueIndex] > 10) ? Color.Red : Color.Blue;
}
Related
I'm writing a custom control for WPF (a drawn one) and I'm having massive performance issues. The fact is that I'm drawing a lots of text and this might be a part of the problem. I timed the OnRender method however, and I'm faced with very weird results - the whole method (especially after moving to GlyphRun implemenation) takes around 2-3ms to complete. Everything looks like following (take a look at the Output window for debug timing results) (requires Flash to play):
https://www.screencast.com/t/5p6mC6rxFv0
The OnRender method doesn't have anything special in particular, it just renders some rectangles and text:
protected override void OnRender(DrawingContext drawingContext)
{
var stopwatch = Stopwatch.StartNew();
ValidateMetrics();
base.OnRender(drawingContext);
var pixelsPerDip = VisualTreeHelper.GetDpi(this).PixelsPerDip;
// Draw header
DrawHeader(drawingContext, pixelsPerDip);
// Draw margin
DrawMargin(drawingContext, pixelsPerDip);
// Draw data
DrawData(drawingContext, pixelsPerDip);
// Draw footer
DrawFooter(drawingContext);
stopwatch.Stop();
System.Diagnostics.Debug.WriteLine($"Drawing took {stopwatch.ElapsedMilliseconds}ms");
}
I ran Visual Studio's performance analysis and got the following results:
Clearly "Layout" of the editor control takes a lot of time, but still rendering is very quick.
How to debug this performance issue further? Why performance is so low despite OnRender taking milliseconds to run?
Edit - as response to comments
I didn't find a good way to time drawing, though I found, what was the cause of the problem and it turned out to be text drawing. I ended up with using very low-level text drawing mechanism, using GlyphRuns and it turned out to be fast enough to display full HD worth of text at least 30 frames per second, what was enough for me.
I'll post some relevant pieces of code below. Please note though: this is not ready-to-use solution, but should point anyone interested in the right direction.
private class GlyphRunInfo
{
public GlyphRunInfo()
{
CurrentPosition = 0;
}
public void FillMissingAdvanceWidths()
{
while (AdvanceWidths.Count < GlyphIndexes.Count)
AdvanceWidths.Add(0);
}
public List<ushort> GlyphIndexes { get; } = new List<ushort>();
public List<double> AdvanceWidths { get; } = new List<double>();
public double CurrentPosition { get; set; }
public double? StartPosition { get; set; }
}
// (...)
private void BuildTypeface()
{
typeface = new Typeface(FontFamily);
if (!typeface.TryGetGlyphTypeface(out glyphTypeface))
{
typeface = null;
glyphTypeface = null;
}
}
private void AddGlyph(char c, double position, GlyphRunInfo info)
{
if (glyphTypeface.CharacterToGlyphMap.TryGetValue(c, out ushort glyphIndex))
{
info.GlyphIndexes.Add(glyphIndex);
if (info.GlyphIndexes.Count > 1)
info.AdvanceWidths.Add(position - info.CurrentPosition);
info.CurrentPosition = position;
if (info.StartPosition == null)
info.StartPosition = info.CurrentPosition;
}
}
private void DrawGlyphRun(DrawingContext drawingContext, GlyphRunInfo regularRun, Brush brush, double y, double pixelsPerDip)
{
if (regularRun.StartPosition != null)
{
var glyphRun = new GlyphRun(glyphTypeface,
bidiLevel: 0,
isSideways: false,
renderingEmSize: FontSize,
pixelsPerDip: (float)pixelsPerDip,
glyphIndices: regularRun.GlyphIndexes,
baselineOrigin: new Point(Math.Round(regularRun.StartPosition.Value),
Math.Round(glyphTypeface.Baseline * FontSize + y)),
advanceWidths: regularRun.AdvanceWidths,
glyphOffsets: null,
characters: null,
deviceFontName: null,
clusterMap: null,
caretStops: null,
language: null);
drawingContext.DrawGlyphRun(brush, glyphRun);
}
}
And then some random code, which used the prior methods:
var regularRun = new GlyphRunInfo();
var selectionRun = new GlyphRunInfo();
// (...)
for (int ch = 0, index = line * Document.BytesPerRow; ch < charPositions.Count && index < availableBytes; ch++, index++)
{
byte drawnByte = dataBuffer[index];
char drawnChar = (drawnByte < 32 || drawnByte > 126) ? '.' : (char)drawnByte;
if (enteringMode == EnteringMode.Overwrite && (selection?.IsCharSelected(index + offset) ?? false))
AddGlyph(drawnChar, charPositions[ch].Position.TextCharX, selectionRun);
else
AddGlyph(drawnChar, charPositions[ch].Position.TextCharX, regularRun);
}
regularRun.FillMissingAdvanceWidths();
selectionRun.FillMissingAdvanceWidths();
DrawGlyphRun(drawingContext, regularRun, SystemColors.WindowTextBrush, linePositions[line].TextStartY, pixelsPerDip);
DrawGlyphRun(drawingContext, selectionRun, SystemColors.HighlightTextBrush, linePositions[line].TextStartY, pixelsPerDip);
I would like to cut the extra text and display three dots(...) and when user clicks on the cell, everthing has to be displayed. how to calculate the width of the property grid cell and cut the text. Any help will be grateful.
Pictures are attached for explanation
Instead of this
I would like to achieve this
and it should vary according to the cell size
The property grid does not allow that and you cannot customize it to do so using any official way.
However, here is some sample code that seems to work. It uses a TypeConverter to reduce the value from the grid's size.
Use at your own risk as it relies on PropertyGrid's internal methods and may have an impact on performance, since it requires a refresh on the whole grid on each resize.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
// note this may have an impact on performance
propertyGrid1.SizeChanged += (sender, e) => propertyGrid1.Refresh();
var t = new Test();
t.MyStringProperty = "The quick brown fox jumps over the lazy dog";
propertyGrid1.SelectedObject = t;
}
}
public class AutoSizeConverter : TypeConverter
{
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value == null)
return null;
// small trick to get PropertyGrid control (view) from context
var view = (Control)context.GetService(typeof(IWindowsFormsEditorService));
// bigger trick (hack) to get value column width & font
int width = (int)view.GetType().GetMethod("GetValueWidth").Invoke(view, null);
var font = (Font)view.GetType().GetMethod("GetBoldFont").Invoke(view, null); // or GetBaseFont
// note: the loop is not super elegant and may probably be improved in terms of performance using some of the other TextRenderer overloads
string s = value.ToString();
string ellipsis = s;
do
{
var size = TextRenderer.MeasureText(ellipsis, font);
if (size.Width < width)
return ellipsis;
s = s.Substring(0, s.Length - 1);
ellipsis = s + "...";
}
while (true);
}
}
public class Test
{
// we use a custom type converter
[TypeConverter(typeof(AutoSizeConverter))]
public string MyStringProperty { get; set; }
}
Here is the result (supports resize):
I can see that when moving mouse over series titles in ViewXY's LegendBox, the series gets highlighted.
I'm using WPF chart. I have several series that I'd like to highlight in the same time.
How can this be achieved?
LegendBox class has SeriesTitle* events. You can utilize it pretty much like this:
m_chart.BeginUpdate();
ViewXY viewXY = m_chart.ViewXY;
viewXY.XAxes[0].ValueType = AxisValueType.Number;
int seriesCount = 10;
//Create series that will highlight the other series
for (int i = 0; i < seriesCount; i++)
{
PointLineSeries s = new PointLineSeries(viewXY, viewXY.XAxes[0], viewXY.YAxes[0]);
s.LineStyle.Color = DefaultColors.SeriesForBlackBackgroundWpf[i];
s.Points = GenerateSomeRandomData((i+1) * 20);
s.Title.Text = "Series " + i.ToString();
viewXY.PointLineSeries.Add(s);
}
viewXY.LegendBox.MoveFromSeriesTitle = false;
viewXY.LegendBox.SeriesTitleMouseClick += LegendBox_SeriesTitleMouseClick;
viewXY.LegendBox.Layout = LegendBoxLayout.Vertical;
m_chart.EndUpdate();
and defining the event handler like this:
void LegendBox_SeriesTitleMouseClick(object sender, System.Windows.RoutedEventArgs e)
{
m_chart.BeginUpdate();
foreach (PointLineSeries s in m_chart.ViewXY.PointLineSeries)
{
s.SetHighlight();
//s.RemoveHighlight(); //To remove highlight, use this
}
m_chart.EndUpdate();
}
Then click on any series title, and all the series get highlighted. Based on series.MouseHighlight property setting, it changes to brighter thick line, animated color from bright to dark, or keeps original color.
Hopefully this helps :-)
I have an array of horizontal fields which contains a bitmap and a labelfield each. The whole row should be clickable which is working so far, but how can I set the focus color properly? At the moment the onFocus and onUnfocus functions are being completely ignored.
This is the definition of my array:
for (int i = 0; i < listSize; i++) {
logInDetailManager[i] = new HorizontalFieldManager(
Manager.USE_ALL_WIDTH | Field.FOCUSABLE) {
protected void onFocus(int direction) {
super.onFocus(direction);
background_color = Color.RED;
invalidate();
}
protected void onUnfocus() {
invalidate();
background_color = Color.GREEN;
}
And this is how I add my horizontal fields:
logInDetailManager[i].setChangeListener(this);
logInDetailManager[i].add(dummyIcon[i]);
logInDetailManager[i].add(new LabelField("hello"));
logInDetailManager[i].add(new NullField(Field.FOCUSABLE));
add(logInDetailManager[i]);
Sorry, I couldn't comment to my own post yesterday since I'm new to Stackoverflow ;)
Here's how I solved it:
I removed onFocus() and onUnfocus() from the HFM and set the background color in the paint method so the whole row color is changed when focused:
protected void paint(Graphics graphics) {
graphics.setBackgroundColor(isFocus() ? Color.RED : Color.GREEN);
graphics.clear();
invalidate();
super.paint(graphics);
}
I also found out that if you want to set more complex backgrounds (i.e. with a gradient) you can also use the setBackground(int visual, Background background) method:
Background bg_focus = (BackgroundFactory
.createLinearGradientBackground(Color.GREEN, Color.LIGHTGREEN,
Color.LIGHTGREEN, Color.GREEN));
loginDetailManager[i].setBackground(VISUAL_STATE_FOCUS, bg_focus);
Make sure to delete you're paint method when using the setBackground function like that!
For a System.Windows.Forms.TextBox with Multiline=True, I'd like to only show the scrollbars when the text doesn't fit.
This is a readonly textbox used only for display. It's a TextBox so that users can copy the text out. Is there anything built-in to support auto show of scrollbars? If not, should I be using a different control? Or do I need to hook TextChanged and manually check for overflow (if so, how to tell if the text fits?)
Not having any luck with various combinations of WordWrap and Scrollbars settings. I'd like to have no scrollbars initially and have each appear dynamically only if the text doesn't fit in the given direction.
#nobugz, thanks, that works when WordWrap is disabled. I'd prefer not to disable wordwrap, but it's the lesser of two evils.
#André Neves, good point, and I would go that way if it was user-editable. I agree that consistency is the cardinal rule for UI intuitiveness.
I came across this question when I wanted to solve the same problem.
The easiest way to do it is to change to System.Windows.Forms.RichTextBox. The ScrollBars property in this case can be left to the default value of RichTextBoxScrollBars.Both, which indicates "Display both a horizontal and a vertical scroll bar when needed." It would be nice if this functionality were provided on TextBox.
Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox onto your form. It's not quite perfect but ought to work for you.
using System;
using System.Drawing;
using System.Windows.Forms;
public class MyTextBox : TextBox {
private bool mScrollbars;
public MyTextBox() {
this.Multiline = true;
this.ReadOnly = true;
}
private void checkForScrollbars() {
bool scroll = false;
int cnt = this.Lines.Length;
if (cnt > 1) {
int pos0 = this.GetPositionFromCharIndex(this.GetFirstCharIndexFromLine(0)).Y;
if (pos0 >= 32768) pos0 -= 65536;
int pos1 = this.GetPositionFromCharIndex(this.GetFirstCharIndexFromLine(1)).Y;
if (pos1 >= 32768) pos1 -= 65536;
int h = pos1 - pos0;
scroll = cnt * h > (this.ClientSize.Height - 6); // 6 = padding
}
if (scroll != mScrollbars) {
mScrollbars = scroll;
this.ScrollBars = scroll ? ScrollBars.Vertical : ScrollBars.None;
}
}
protected override void OnTextChanged(EventArgs e) {
checkForScrollbars();
base.OnTextChanged(e);
}
protected override void OnClientSizeChanged(EventArgs e) {
checkForScrollbars();
base.OnClientSizeChanged(e);
}
}
I also made some experiments, and found that the vertical bar will always show if you enable it, and the horizontal bar always shows as long as it's enabled and WordWrap == false.
I think you're not going to get exactly what you want here. However, I believe that users would like better Windows' default behavior than the one you're trying to force. If I were using your app, I probably would be bothered if my textbox real-estate suddenly shrinked just because it needs to accomodate an unexpected scrollbar because I gave it too much text!
Perhaps it would be a good idea just to let your application follow Windows' look and feel.
There's an extremely subtle bug in nobugz's solution that results in a heap corruption, but only if you're using AppendText() to update the TextBox.
Setting the ScrollBars property from OnTextChanged will cause the Win32 window (handle) to be destroyed and recreated. But OnTextChanged is called from the bowels of the Win32 edit control (EditML_InsertText), which immediately thereafter expects the internal state of that Win32 edit control to be unchanged. Unfortunately, since the window is recreated, that internal state has been freed by the OS, resulting in an access violation.
So the moral of the story is: don't use AppendText() if you're going to use nobugz's solution.
I had some success with the code below.
public partial class MyTextBox : TextBox
{
private bool mShowScrollBar = false;
public MyTextBox()
{
InitializeComponent();
checkForScrollbars();
}
private void checkForScrollbars()
{
bool showScrollBar = false;
int padding = (this.BorderStyle == BorderStyle.Fixed3D) ? 14 : 10;
using (Graphics g = this.CreateGraphics())
{
// Calcualte the size of the text area.
SizeF textArea = g.MeasureString(this.Text,
this.Font,
this.Bounds.Width - padding);
if (this.Text.EndsWith(Environment.NewLine))
{
// Include the height of a trailing new line in the height calculation
textArea.Height += g.MeasureString("A", this.Font).Height;
}
// Show the vertical ScrollBar if the text area
// is taller than the control.
showScrollBar = (Math.Ceiling(textArea.Height) >= (this.Bounds.Height - padding));
if (showScrollBar != mShowScrollBar)
{
mShowScrollBar = showScrollBar;
this.ScrollBars = showScrollBar ? ScrollBars.Vertical : ScrollBars.None;
}
}
}
protected override void OnTextChanged(EventArgs e)
{
checkForScrollbars();
base.OnTextChanged(e);
}
protected override void OnResize(EventArgs e)
{
checkForScrollbars();
base.OnResize(e);
}
}
What Aidan describes is almost exactly the UI scenario I am facing. As the text box is read only, I don't need it to respond to TextChanged. And I'd prefer the auto-scroll recalculation to be delayed so it's not firing dozens of times per second while a window is being resized.
For most UIs, text boxes with both vertical and horizontal scroll bars are, well, evil, so I'm only interested in vertical scroll bars here.
I also found that MeasureString produced a height that was actually bigger than what was required. Using the text box's PreferredHeight with no border as the line height gives a better result.
The following seems to work pretty well, with or without a border, and it works with WordWrap on.
Simply call AutoScrollVertically() when you need it, and optionally specify recalculateOnResize.
public class TextBoxAutoScroll : TextBox
{
public void AutoScrollVertically(bool recalculateOnResize = false)
{
SuspendLayout();
if (recalculateOnResize)
{
Resize -= OnResize;
Resize += OnResize;
}
float linesHeight = 0;
var borderStyle = BorderStyle;
BorderStyle = BorderStyle.None;
int textHeight = PreferredHeight;
try
{
using (var graphics = CreateGraphics())
{
foreach (var text in Lines)
{
var textArea = graphics.MeasureString(text, Font);
if (textArea.Width < Width)
linesHeight += textHeight;
else
{
var numLines = (float)Math.Ceiling(textArea.Width / Width);
linesHeight += textHeight * numLines;
}
}
}
if (linesHeight > Height)
ScrollBars = ScrollBars.Vertical;
else
ScrollBars = ScrollBars.None;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
}
finally
{
BorderStyle = borderStyle;
ResumeLayout();
}
}
private void OnResize(object sender, EventArgs e)
{
m_timerResize.Stop();
m_timerResize.Tick -= OnDelayedResize;
m_timerResize.Tick += OnDelayedResize;
m_timerResize.Interval = 475;
m_timerResize.Start();
}
Timer m_timerResize = new Timer();
private void OnDelayedResize(object sender, EventArgs e)
{
m_timerResize.Stop();
Resize -= OnResize;
AutoScrollVertically();
Resize += OnResize;
}
}