I have a WPF app with a "Grid Window". This window has no added XAML to it. I create a grid (columns and rows) then place a rectangle in each one all in C#.
This allows me to make a grid where I set the 'Stroke' and show locations on the grid when I set the 'Fill'.
The entire grid is set the same, in other words, if one part of the grid is red, the whole grid is red. Currently I set the grid by iterating through all of the rectangles and setting the 'Stroke' property. That works fine but seems very slow compared to most of the other operations. I would like to bind the stroke property to a variable in the C# (unless iterating is a reasonable way to handle it).
I have looked at quite a few questions here, but most want to use XAML. My code below is based off of Binding without XAML [WPF]. No errors, the grid just never shows up.
// put a rectangle in each square
for (int i = 0; i < x; i++) // for each column
{
for (int j = 0; j < y; j++) // for each row
{
// create a new rectangle with name, height, width, and starting color (transparent)
var rect = new Rectangle()
{
Name = $"rec{(i + 1).ToString("00")}{(j + 1).ToString("00")}", //column 5 row 2 -> rec0502
Height = squareSize,
Width = squareSize,
Fill = _ColorOff
};
// create the binding
var binder = new Binding
{
Source = _GridColor, // Brush that is updated on color change
Path = new PropertyPath("Stroke")
};
// apply the binding to the rectangle
rect.SetBinding(Rectangle.StrokeProperty, binder);
rect.DataContext = binder;
// place the rectangle
Grid.SetColumn(rect, i); // current column
Grid.SetRow(rect, (y - j - 1)); // same row but from the bottom (puts point 0,0 at bottom left)
// add the rectangle to the grid
grdBattleGrid.Children.Add(rect);
}
}
Even if iterating is fine, I'd still like to know what I'm doing wrong.
EDIT: The color name is chosen from a ComboBox on a separate window. This updates the user settings, which in turn throws an event my "Grid Window" is subscribed to. I convert the name to a SolidColorBrush before iterating though the rectangles.
The most simple solution would be not to have any Binding at all. Assign _GridColor to the Rectangle's Stroke. Whenever the Color property of (the assumed SolidColorBrush) _GridColor changes, it affects all Rectangles.
public SolidColorBrush _GridColor { get; } = new SolidColorBrush();
...
var rect = new Rectangle
{
Name = $"rec{(i + 1).ToString("00")}{(j + 1).ToString("00")}",
Height = squareSize,
Width = squareSize,
Fill = _ColorOff,
Stroke = _GridColor // here
};
Grid.SetColumn(rect, i);
Grid.SetRow(rect, (y - j - 1));
grdBattleGrid.Children.Add(rect);
Change the Rectangle Stroke by assigning a value to the Color property of the _GridColor Brush:
_GridColor.Color = Colors.Red;
You just need to set the DataContext one time for the Window, not for each Rectangle.
Is Binding your own view model class from INotifyPropertyChanged interface? Link to MS Docs
Related
I added a new chart control (System.Windows.Forms.DataVisualiation.Charting) with ChartType Bar.
As requirement the label text must be white and into the bar value. Therefore I set the BarLabelStyle=Right in the CustomProperties of the DataPoint objects and the LabelForeColor to White.
See the below images.
The label in the 2nd gray bar is correctly shown.
The first bar instead is too small and the white text is shown out on the right side but is not visible.
However, when the bar is too short, the label text is positioned outside the bar and the text cannot be seen using white color.
Is there a way to check when the label text is drawn outside the bar value so that I can change the color (e.g. black)?
Thanks.
Unfortunately MCChart has almost no capacities wrt to dynamic expressions.
To work around you can either..:
Code the ForeColor depending on the y-value your DataPoints has. Either right when you add them or in a function that loops over all points, whenever you call it.. - Depending on the Font, the axis range and the label text this could be some threshold number you have to determine.
Example:
int p = yourSeries.Points.AddXY(...);
yourSeries.Points[p].LabelForeColor = yourSeries.Points[p].YValues[0] < threshold ?
Color.Black : Color.White;
Or you can cheat a little ;-)
You can set the LabelBackColor to have the same color as the Series, i.e. the bar itself. Here is is how to do that:
To access the Series.Color we have to call:
chart.ApplyPaletteColors();
Now we can set
yourSeries.LabelForeColor = Color.White;
yourSeries.LabelBackColor = yourSeries.Color;
Example:
Update:
Since you can't use the cheat you will have to set the colors.
The challenge is to know just how much space each label's text needs compared to how much space the bars have. The former can be measured (TextRenderer.MeasureString()) and the latter can be extracted from the y-axis (Axis.ValueToPixelPosition()).
Here is a function to do that; it is a little more complicated than I had hoped for, mostly because it tries to be generic..
void LabelColors(Chart chart, ChartArea ca, Series s)
{
if (chart.Series.Count <= 0 || chart.Series[0].Points.Count <= 0) return;
Axis ay = ca.AxisY;
// get the maximum & minimum values
double maxyv = ay.Maximum;
if (maxyv == double.NaN) maxyv = s.Points.Max(v => v.YValues[0]);
double minyv = s.Points.Min(v => v.YValues[0]);
// get the pixel positions of the minimum
int y0x = (int)ay.ValueToPixelPosition(0);
for (int i = 0; i < s.Points.Count; i++)
{
DataPoint dp = s.Points[i];
// pixel position of the bar right
int vx = (int)ay.ValueToPixelPosition(dp.YValues[0]);
// now we knowe the bar's width
int barWidth = vx - y0x;
// find out what the label text actauly is
string t = dp.LabelFormat != "" ?
String.Format(dp.LabelFormat, dp.YValues[0]) : dp.YValues[0].ToString();
string text = dp.Label != "" ? dp.Label : t;
// measure the (formatted) text
SizeF rect = TextRenderer.MeasureText(text, dp.Font);
Console.WriteLine(text);
dp.LabelForeColor = barWidth < rect.Width ? Color.Black : Color.White;
}
}
I may have overcomplicated the way to get at the text that should show; you certainly can decide if you can simplify for your case.
Note: You must call this function..
whenever your data may have changed
only after the axes of the chart have finished their layout (!)
The former point is obvious, the latter isn't. It means that you can't call the function right after adding your points! Instead you must do it at some later place or else the axis function needed to get the bar size will not work.
MSDN says it can only happen in a PaintXXX event; I found that all mouse events also work and then some..
To be save I'll put it in the PostPaint event:
private void chart_PostPaint(object sender, ChartPaintEventArgs e)
{
LabelColors(chart, chart.ChartAreas[0], chart.Series[0]);
}
Using VisualStudio WindowsForms Form. Creating Chart control in designer.
I'm trying to add some customLabels on charts Axis along WITH the default labels.
To do so I add customLabels with RowIndex property =1. Thus I see default labels AND my customLabels.
Now the problem is that while the default labels are rotated correctly my custom labels are not.
The Axis property LabelStyle.Angle affects only labels that are in RowIndex = 0, i.e. default labels.
And if I put customLabels at RowIndex=0 - all default labels will disappear.
What I see:
What I want to see:
I see no way to do that, really. The reason is probably that the developers decided there simply cannot be enough space for horizontal labels, once you start putting them in one or more extra rows..
Here is a workaround: Make those CustomLabels all transparent and draw them as you like in a xxxPaint event.
Here is an example:
I prepared the CustomLabels :
CustomLabel cl = new CustomLabel();
cl.ForeColor = Color.Transparent;
cl.RowIndex = 1;
...
And I code the drawing like this:
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
Axis ay = chart1.ChartAreas[0].AxisY;
foreach (var cl in ay.CustomLabels)
{
if (cl.RowIndex == 1)
{
double vy = (cl.ToPosition + cl.FromPosition) / 2d;
int y = (int)ay.ValueToPixelPosition(vy);
e.ChartGraphics.Graphics.DrawString(cl.Text,
cl.Axis.TitleFont, Brushes.DarkRed, 0, y);
}
}
}
Do use a Font and Brush of your own liking. Here is the result:
Note that you may need to create more space to the left by tweaking the ChartArea.Position or the ChartArea.InnerPlotPosition, both of which are in 100% and, as usual, default to 'auto'.
For even more rows you need to change the check to cl.RowIndex < 0 and find a nice x position value for the DrawString.
I am trying to add columns and gridsplitters to a grid, but can't get the exact behavior.
After the user specifies where he wants a vertical splitter to appear:
// Get current Col0 width, size new col and existing Col0
double col0Width = LayoutRoot.ColumnDefinitions[0].ActualWidth;
double newCol0Width = col0Width - 5 - pt.X;
LayoutRoot.ColumnDefinitions[0].Width = new GridLength(newCol0Width);
// New Column 0
var c = new ColumnDefinition();
c.Width = new GridLength(pt.X);
LayoutRoot.ColumnDefinitions.Insert(0, c);// Attach GridSplitter to left edge of existing first column
var gss = new GridSplitter();
gss.Background = new SolidColorBrush(Colors.DarkSlateBlue);
gss.Width = 5; gss.Cursor = Cursors.ScrollWE;
gss.ResizeBehavior = GridResizeBehavior.BasedOnAlignment;
gss.HorizontalAlignment = HorizontalAlignment.Left;
LayoutRoot.Children.Add(gss);
// Add to current left-most colunn
Grid.SetColumn(gss, 0);
// Create new column, insert
// New Column 0
var c = new ColumnDefinition();
c.Width = new GridLength(pt.X);
LayoutRoot.ColumnDefinitions.Insert(0, c);
// Move existing content from Col 0 to new Col 1.
I can repeat this and create an arbitrary number of vertical splitters.
The required resize behavior: moving a splitter resizes only the columns immed. to the left and right of the splitter.
The current resize behavior: moving a splitter treats everything to the right of the splitter as one object, expanding or shrinking the column to the left of the splitter, while moving everything to the right. That is, if there are 3 columns, moving the left-most splitter appears to push col 2 to the right and shrinking col 3, without resizing col 2.
(I hope I explained that clearly enough.)
I have tried putting the GridSplitters in their own columns, and tried various GridResizeBehaviors, but haven't found the correct combination.
Any tips would be appreciated....
And a related question: In an event handler for GridSplitter's OnDragDelta, is there a way to stop the splitter from traveling any further in a certain direction? I would like to prevent them from shrinking the right-most column below a certain width, while allowing them to move the splitter back to the left.
Thanks.
As my comment suggested, it looks like the columns need to be '*' sized.
So, after adding the new column and splitter, I fixed up the widths like this (first clumsy cut at solution):
// Get all col widths, set appropriate '*' sizes.
foreach (ColumnDefinition col in LayoutRoot.ColumnDefinitions)
{
colWidths.Add(col.Width);
total += col.Width.Value;
Debug.WriteLine($" Width : {col.Width}");
}
Debug.WriteLine($"{total}");
for (int i = 0; i < LayoutRoot.ColumnDefinitions.Count; i++)
{
double d = colWidths[i].Value;
double ratio = d / total;
int ii = (int) (ratio * 100);
LayoutRoot.ColumnDefinitions[i].Width = new GridLength(ii, GridUnitType.Star);
}
I am trying to write a small game, it works pretty good for now, but I need something like a collision detection and for that detection it is important to know the excat size of that unicode character(may be converted to a graphic, to work better with it, instead of using a label ?!)
I thought it would be great, to get all the coordinates of that element where it is black and leave out where it is transparent.Maybe something based on this, but I am m not quite sure wether it is a good and fast approach. By the way it is important to get the coordinates based on that canvas.
What I have so far:
The unicode characters e.g.:
"\U0001F6B5"
The element for the canvas, I could also use a graphical object:
var customLabel = new Label
{
Uid= all[i].ToString().Equals("Biker")?"Biker":"",
TabIndex = i,
Margin = new Thickness(0),
Content = all[i].Icon,//"\U0001F6B5"
BorderThickness = new Thickness(2.0),
Name = controlName,
FontSize = 22,
Padding = new Thickness(0.0, 0.0, 0.0, 0.0),
ToolTip = tooltip, //may be tooltip with real pictures from that object in real life and a "Registerkare" with all its properties(armor, power,...)
//BorderBrush = Brushes.Red //to see the size of this element
Cursor = Cursors.Hand
// FlowDirection= //use if the object moves left, right, top, butoom ?
//IsHitTestVisible= //use for collision ?
};
//Setting the position on the canvas:
Canvas.SetLeft(customLabel, xRef); //X
Canvas.SetTop(customLabel, yRef); //Y
At the moment I came up with that solution(seeing an element as a rectangle), I use it only once, when I create a random map(later the player will check against those saved positions, via "INotifyPropertyChanged" if his position changed):
for (int xx = 1; xx <= customLabel.Width; xx++)//range for object
{
for (int yy = 1; yy <= customLabel.Height; yy++)//range for object
{
coordinatesInUse.Add(new KeyValuePair<int, int>((int)xRef+xx, (int)xRef+yy));
var lookup = coordinatesInUse.ToLookup(kvp => kvp.Key, kvp => kvp.Value);
foreach (int look in lookup[1])
{
//checking wether that element exist or not
}
}
}
Create a custom class consisting of polygon(rectangle), x-cordinate and y-cordinate. Then List will be your collection of element,x and y coordinates put together. Instead of creating a new KeyValue Pair everytime, you can use the same collection all over and just change the values as needed.
I don't know if can make it clear otherwise, So here is the code:-
Series series2 = null;
for (int i = 0; i < 4; i++)
{
series2 = chart2.Series.Add(subjectnames[i]);
}
for (int i = 0; i < 4; i++)
{
series2.Points.Add(attemptper[i]);
series2.Points.Add(correctper[i]);
}
Now, I just want to display "attemptper" bars in different color than "correctper" bars. By default they are appearing in greyish-blue color. How do I get it done?
The charts appear all in bluish color. I want them to appear in different colors. I think I haven't added the data correctly to the chart for this to achieve?
You change the color on the series point(index) like this:
Chart1.Series[1].Points[0].Color = Color.Red
Getting the chart to repaint might also take some code, depending on what you're doing. For me, I wanted to animate my chart, building the data point (a column) dynamically as the program ran, showing the status of some work I was doing, which required this:
Chart1.Series.ResumeUpdates()
Chart1.Series[1].Points.Item[0].YValues = Double(1) {MyNewValue, 0}
Chart1.Series[1].Points[0].Color = Color.Red
Chart1.DataBind()
Chart1.Series.Invalidate()
Chart1.Series.SuspendUpdates()
I would suggest adding an extra series to your chart and changing the color set on that.