So I made a custom control, containing a button and an image. I create 64 of them, a chessboard.
for (int i = 1; i <= 8; i++)
{
for (int j = 1; j <= 8; j++)
{
Square field = new Square(i, j, this);
field.Click += OnFieldClick;
squares[i - 1, j - 1] = field;
squares_list.Add(field);
Square_control fieldBase = new Square_control(field);
this.table.Children.Add(fieldBase);
Grid.SetRow(fieldBase, j - 1);
Grid.SetColumn(fieldBase, i - 1);
}
}
Square is a class inheriting from Button class. It's background is set in it's constructor.
Square_Control is my custom control containing a button and an image. Its constructor sets the button to the parameter.
Thanks to the debugger, I found out that colors black and white are set properly in the field and fieldBase objects, but when I run the program all the buttons are white.
I feel like I'm missing some important knowledge about how WPF works.
Square constructor:
public Square(int row, int col, MainWindow wind)
{
if ((row + col) % 2 == 0)
isWhite = true;
else
isWhite = false;
colName = col;
rowName = row;
if (this.isWhite)
SquareColor = wind.BasicWhite;
else
SquareColor = wind.BasicBlack;
Background = SquareColor;
window = wind;
}
BasicWhite = new SolidColorBrush(new Color()
{
R = 255,
B = 255,
G = 255,
A = 255
});
BasicBlack = new SolidColorBrush(new Color()
{
R = 0,
B = 0,
G = 0,
A = 255
});
Square_control xaml:
<Button x:Name="SQR">
<Image x:Name="FigImage"/>
</Button>
It's constructor:
public Square_control(Button butt)
{
InitializeComponent();
SQR = butt;
}
Also, I tried setting background color in Square_Control directly in XAML and it worked.
You're not doing it properly indeed.
First, you already have a Square control; what do you need a Square_control for?
You don't even need Square; just make a new UserControl and put your Button and your Image in it.
If you need further specialization (like the IsWhite property), you can just add it to this UserControl as dependency properties.
And simply pass the required parameters (x, y) to its constructor.
You can start with something like that:
public partial class ChessSquare : UserControl
{
public enum Piece
{
King,
Queen,
Rook,
Bishop,
Knight,
Pawn,
None
}
public readonly SolidColorBrush BasicWhite = new SolidColorBrush(new Color { R = 255, G = 255, B = 255, A = 255 });
public readonly SolidColorBrush BasicBlack = new SolidColorBrush(new Color { R = 0, G = 0, B = 0, A = 255 });
public Boolean IsWhite
{
get { return (Boolean)this.GetValue(IsWhiteProperty); }
set
{
this.SetValue(IsWhiteProperty, value);
this.Background = value ? BasicWhite : BasicBlack;
}
}
public static readonly DependencyProperty IsWhiteProperty = DependencyProperty.Register(
nameof(IsWhite), typeof(Boolean), typeof(ChessSquare), new PropertyMetadata(false, new PropertyChangedCallback(OnIsWhitePropertyChanged)));
public Piece PieceType
{
get { return (Piece)this.GetValue(PieceProperty); }
set { this.SetValue(PieceProperty, value); }
}
public static readonly DependencyProperty PieceProperty = DependencyProperty.Register(
nameof(PieceType), typeof(Piece), typeof(ChessSquare), new PropertyMetadata(Piece.None, new PropertyChangedCallback(OnPieceTypePropertyChanged)));
public ChessSquare(int row, int col)
{
InitializeComponent();
IsWhite = (row + col) % 2 == 0;
}
private static void OnIsWhitePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var square = (ChessSquare)d;
square.Background = (bool)e.NewValue ? square.BasicWhite : square.BasicBlack;
}
private static void OnPieceTypePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// change the image, set it to null is the value is Piece.None
}
}
And eventually replace the this.Background by this.button.Background, depending on how you name stuff in this UserControl's associated XAML.
Related
I made a customcontrol inherit from TextBlock for LetterSpacing, here are my codes:
public class NewTextBlock : TextBlock
{
public NewTextBlock() : base() {
var dp = DependencyPropertyDescriptor.FromProperty(
TextBlock.TextProperty,
typeof(TextBlock));
dp.AddValueChanged(this, (sender, args) =>
{
LetterSpacingChanged(this);
});
}
static void LetterSpacingChanged(DependencyObject d)
{
NewTextBlock TB = d as NewTextBlock;
TB.TextEffects.Clear();
for (int i = 1; i <= TB.Text.Length; i++)
{
TranslateTransform transform = new TranslateTransform(TB.LetterSpacing, 0);
TextEffect effect = new TextEffect();
effect.Transform = transform;
effect.PositionStart = i;
effect.PositionCount = TB.Text.Length;
TB.TextEffects.Add(effect);
if (effect.CanFreeze)
{
effect.Freeze();
}
}
}
public double LetterSpacing
{
get { return (double)GetValue(LetterSpacingProperty); }
set { SetValue(LetterSpacingProperty, value); }
}
// Using a DependencyProperty as the backing store for LetterSpacing. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LetterSpacingProperty =
DependencyProperty.Register("LetterSpacing", typeof(double), typeof(NewTextBlock), new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(LetterSpacingChanged)));
private static void LetterSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LetterSpacingChanged(d);
}
}
Now when I use it like this:
<Test:NewTextBlock Background="Red" HorizontalAlignment="Center" VerticalAlignment="Center" LetterSpacing="5" Text="123123123123">
<Test:NewTextBlock.LayoutTransform>
<RotateTransform Angle="45"></RotateTransform>
</Test:NewTextBlock.LayoutTransform>
</Test:NewTextBlock>
It turns out to be like this:
enter image description here
As the red background showed, it looks like the actual width of the NewTextBlock does not include the width of the TextEffect.
I want to find a way to get the actual width of the TextEffect and set a right actual width to the NewTextBlock.
How can I do this?
Don't use the DependencyPropertyDescriptor to register a property changed callback. If you are not careful (which is the case with your code) it will create a (potentially severe) per instance memory leak.
Better override the the dependency property's metadata in a static constructor to register a new callback (see example below).
TextBlock does not support custom character spacing. TextBlock completely relies on the provided font (or typeface) to calculate the actual text width. Since MeasureOverride is sealed, you must force the new Width after a change of the Text value or any text related property change in general (for example FontSize etc.).
This means, to implement the sizing behavior correctly you would have to override the property metadata for all relevant properties to trigger the recalculations of the actual NewTextBlock width. You can follow the metadata override for the TextBlock.TextProperty property of the below example.
To calculate the width of a formatted string you usually use the FormattedText class (see below). In your case, you would have to include the current character spacing into the computation.
public class NewTextBlock : TextBlock
{
public double LetterSpacing
{
get => (double)GetValue(LetterSpacingProperty);
set => SetValue(LetterSpacingProperty, value);
}
public static readonly DependencyProperty LetterSpacingProperty = DependencyProperty.Register(
"LetterSpacing",
typeof(double),
typeof(NewTextBlock),
new FrameworkPropertyMetadata(default(double), OnLetterSpacingChanged));
static NewTextBlock()
{
TextProperty.OverrideMetadata(
typeof(NewTextBlock),
new FrameworkPropertyMetadata(default(string), OnTextChanged));
}
public NewTextBlock()
{
this.TextEffects = new TextEffectCollection();
}
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
=> (d as NewTextBlock).ApplyTextEffects();
private static void OnLetterSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
=> (d as NewTextBlock).ApplyTextEffects();
private void ApplyTextEffects()
{
if (string.IsNullOrWhiteSpace(this.Text))
{
return;
}
this.TextEffects.Clear();
for (int characterIndex = 1; characterIndex <= this.Text.Length; characterIndex++)
{
var transform = new TranslateTransform(this.LetterSpacing, 0);
var effect = new TextEffect
{
Transform = transform,
PositionStart = characterIndex,
PositionCount = this.Text.Length
};
this.TextEffects.Add(effect);
if (effect.CanFreeze)
{
effect.Freeze();
}
}
AdjustCurrentWidth();
}
private void AdjustCurrentWidth()
{
var typeface = new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, this.FontStretch);
double pixelsPerDip = VisualTreeHelper.GetDpi(this).PixelsPerDip;
var formattedText = new FormattedText(this.Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, this.FontSize, this.Foreground, pixelsPerDip);
double totalLetterSpacingWidth = (this.Text.Length - 1) * this.LetterSpacing;
double totalTextWidth = formattedText.WidthIncludingTrailingWhitespace + totalLetterSpacingWidth;
this.Width = totalTextWidth;
}
}
Maybe I missed the point in this STextBlock but it looks to me like you could (maybe should) just use the Glyphs frameworkelement instead.
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/introduction-to-the-glyphrun-object-and-glyphs-element?view=netframeworkdesktop-4.8
https://learn.microsoft.com/en-us/dotnet/api/system.windows.documents.glyphs?view=windowsdesktop-7.0
EG
<!-- "Hello World!" with explicit character widths for proportional font -->
<Glyphs
FontUri = "C:\WINDOWS\Fonts\ARIAL.TTF"
FontRenderingEmSize = "36"
UnicodeString = "Hello World!"
Indices = ",80;,80;,80;,80;,80;,80;,80;,80;,80;,80;,80"
Fill = "Maroon"
OriginX = "50"
OriginY = "225"
/>
<!-- "Hello World!" with fixed-width font -->
<Glyphs
FontUri = "C:\WINDOWS\Fonts\COUR.TTF"
FontRenderingEmSize = "36"
StyleSimulations = "BoldSimulation"
UnicodeString = "Hello World!"
Fill = "Maroon"
OriginX = "50"
OriginY = "300"
/>
I found another solution from the code of #BionicCode
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
if (!string.IsNullOrEmpty(Text))
{
double PositionX = Padding.Left;
double PositionY = Padding.Top;
foreach (var i in Text)
{
var FT = new FormattedText(i.ToString(), CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, new Typeface(FontFamily,FontStyle,FontWeight,FontStretch), FontSize, Foreground);
drawingContext.DrawText(FT, new Point(PositionX, PositionY));
PositionX += FT.Width+LetterSpacing;
var t = Width;
}
}
}
I have a dictionary of buttons and also an null variable button. When you click on an button from the dictionary, we get the values that should be sent to an empty variable.
public static Button selectedfigure=null;
public Dictionary<(int x, int y), Button> _Panels = new Dictionary<(int x, int y), Button>();
public void Fillboard()
{
for (int x = 0; x < 8; x++)
{
for (int y = 0; y < 8; y++)
{
_Panels[(x, y)] = new Button()
{
Height = 64,
Width = 64,
Location = new Point(x * 64, y * 64),
BackColor = CalculateBackColor(x,y),
BackgroundImage = CalculateBackimage(x,y)
};
Controls.Add(_Panels[(x, y)]);
}
}
}
Your question is about how to interact with Mouse events on your "board". The answer is to subscribe to Mouse events like Click for each control you add. Your code is using a normal Button but will be much simpler if you make a custom class that inherits Button or preferably PictureBox and this way each instance keeps track of its own X-Y, color, and image.
Also, instead of using a Dictionary lookup and setting Location properties yourself, you might try an 8x8 TableLayoutPanel for the board, then iterate all its 8 columns and rows with methods that take column and row as arguments.
The mouse events can be subscribed to in the addSquare method.
private void addSquare(int column, int row)
{
var color = ((column + row) % 2) == 0 ? Color.White : Color.Black;
var square = new Square
{
BackColor = color,
Column = column,
Row = row,
Size = new Size(80, 80),
Margin = new Padding(0),
Padding = new Padding(10),
Anchor = (AnchorStyles)0xf,
SizeMode = PictureBoxSizeMode.StretchImage,
};
tableLayoutPanel.Controls.Add(square, column, row);
// Hook the mouse events here
square.Click += onSquareClicked;
square.MouseHover += onSquareMouseHover;
}
Before and after iterating the TableLayoutPanel with addSquare
Changing the value of the Square.Piece property will choose an image from a resource file.
private void initImage(int column, int row)
{
var square = (Square)tableLayoutPanel.GetControlFromPosition(column, row);
if (square.BackColor == Color.Black)
{
switch (row)
{
case 0:
case 1:
case 2:
square.Piece = Piece.Black;
break;
case 5:
case 6:
case 7:
square.Piece = Piece.Red;
break;
default:
square.Piece = Piece.None;
break;
}
}
}
Before and after iterating the TableLayoutPanel with initImage
Mouse event handlers are simple now:
private void onSquareMouseHover(object sender, EventArgs e)
{
var square = (Square)sender;
_tt.SetToolTip(square, square.ToString());
}
private void onSquareClicked(object sender, EventArgs e)
{
var square = (Square)sender;
MessageBox.Show($"Clicked: {square}");
}
ToolTip _tt = new ToolTip();
The Square class is uncomplicated, just a few lines of code.
class Square : PictureBox // Gives more visual control than Button
{
public int Column { get; internal set; }
public int Row { get; internal set; }
Piece _piece = Piece.None;
public Piece Piece
{
get => _piece;
set
{
if(!Equals(_piece, value))
{
_piece = value;
switch (_piece)
{
case Piece.None:
Image = null;
break;
case Piece.Black:
Image = Resources.black;
break;
case Piece.Red:
Image = Resources.red;
break;
}
}
}
}
public override string ToString() =>
Piece == Piece.None ?
$"Empty {BackColor.Name} square [column:{Column} row:{Row}]" :
$"{Piece} piece [column:{Column} row:{Row}]";
}
enum Piece { None, Black, Red };
Edited (in response to Jeremy's excellent suggestion)
Where and how to call the methods to initialize the board:
public MainForm()
{
InitializeComponent();
// Board is a TableLayoutPanel 8 x 8
tableLayoutPanel.AutoSize = true;
tableLayoutPanel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
// Add squares
IterateBoard(addSquare);
// Add pieces
IterateBoard(initImage);
}
void IterateBoard(Action<int, int> action)
{
for (int column = 0; column < 8; column++)
{
for (int row = 0; row < 8; row++)
{
action(column, row);
}
}
}
I don't know exactly which is your problem. I think you need know which button is pressed in your matrix.
You can add the controls in the same way but instead the use of _Panels dictionary, you can use Tag property of button to store a Point with the x,y coordinates:
for (int x = 0; x < 8; x++)
{
for (int y = 0; y < 8; y++)
{
var button = new Button
{
Height = 64,
Width = 64,
Location = new Point(x * 64, y * 64),
BackColor = CalculateBackColor(x,y),
BackgroundImage = CalculateBackimage(x,y),
Tag = new Point(x, y)
};
button.Click += OnButtonClick;
Controls.Add(button);
}
}
In the click event handler of the button:
private void OnButtonClick(object sender, EventArgs e)
{
var button = (Button)sender;
var point = (Point)button.Tag;
// Here you have x,y coordinates of clicked button
}
I'm trying to make a little game where you turn all panels green.
I do this by getting a 5x5 grid of panels, every panel has a 1/3 chance to be green, otherwise it will start as red.
my problem is that i do not have the slightest clue how to start the main problem.
when i click a panel, the panel above, left ,right and below need to change color aswell.
at the moment i do not know how to identify which panels are next to the one being clicked.
this is my code:
public partial class Form1 : Form
{
Panel[,] PanelArray = new Panel[5,5];
Random R = new Random();
int R1;
public Form1()
{
InitializeComponent();
for (int r = 0; r < 5; r++)
{
for (int c = 0; c < 5; c++)
{
R1 = R.Next(0, 3);
PanelArray[r, c] = new Panel
{
Size = new Size(50, 50),
Location = new Point(PanelContainer.Width / 5 * c, PanelContainer.Height / 5 * r),
BackColor = Color.Red,
BorderStyle = BorderStyle.Fixed3D
};
PanelArray[r, c].Click += new EventHandler(PanelArray_Click);
if (R1 == 1) PanelArray[r, c].BackColor = Color.Green;
PanelContainer.Controls.Add(PanelArray[r, c]);
}
}
}
private void PanelArray_Click(object sender, EventArgs e)
{
Panel P = (Panel)sender;
if (P.BackColor == Color.Red) P.BackColor = Color.Green;
else if (P.BackColor == Color.Green) P.BackColor = Color.Red;
if (CheckWin()) MessageBox.Show("test");
}
private bool CheckWin()
{
//foreach panel green blah blah
return false;
}
}
}`
You can use the Tag property in your Panel objects to store some information.
PanelArray[r, c] = new Panel
{
Size = new Size(50, 50),
Location = new Point(PanelContainer.Width / 5 * c, PanelContainer.Height / 5 * r),
BackColor = Color.Red,
BorderStyle = BorderStyle.Fixed3D,
Tag = (Row: r, Column: c)
};
In your PanelArray_Click method, you can get the indexes:
var indexes = ((int Row, int Column))P.Tag;
var row = indexes.Row;
var column = indexes.Column;
// Todo: your logic here
In the Tag property, you can store any object, so you can create some class to store data, if you need.
Other solution is two for loops, to get the indexes, like:
private (int Row, int Column) GetIndexes(Panel panel)
{
for (int x = 0; x < PanelArray.GetLength(0); x++)
{
for (int y = 0; y < PanelArray.GetLength(1); y++)
{
if (PanelArray[x, y] == panel)
{
return (x, y);
}
}
}
throw new Exception("Not found.");
}
And then you can use in your PanelArray_Click method:
var indexes = this.GetIndexes(P);
var row = indexes.Row;
var column = indexes.Column;
// Todo: your logic here
Here is easy trick to get row and column. Create a class that inherits the Panel and adds a row and column. See code below
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
MyPanel[,] PanelArray = new MyPanel[5, 5];
Random R = new Random();
int R1;
public Form1()
{
InitializeComponent();
for (int r = 0; r < 5; r++)
{
for (int c = 0; c < 5; c++)
{
R1 = R.Next(0, 3);
PanelArray[r, c] = new MyPanel
{
Size = new Size(50, 50),
Location = new Point(PanelContainer.Width / 5 * c, PanelContainer.Height / 5 * r),
BackColor = Color.Red,
BorderStyle = BorderStyle.Fixed3D,
row = r,
col = c
};
PanelArray[r, c].Click += new EventHandler(PanelArray_Click);
if (R1 == 1) PanelArray[r, c].BackColor = Color.Green;
PanelContainer.Controls.Add(PanelArray[r, c]);
}
}
}
private void PanelArray_Click(object sender, EventArgs e)
{
MyPanel P = sender as MyPanel;
int row = P.row;
int col = P.col;
if (P.BackColor == Color.Red) P.BackColor = Color.Green;
else if (P.BackColor == Color.Green) P.BackColor = Color.Red;
if (CheckWin()) MessageBox.Show("test");
}
private bool CheckWin()
{
//foreach panel green blah blah
return false;
}
}
public class MyPanel : Panel
{
public int row { get; set; }
public int col { get; set; }
}
}
I have WritableBitmap image and I have set in image control src. I am creating rectangle when user move on the selected text area.I am also using PDFtron SDK to get selected text from the PDF document.
we are getting WritableBitmap image from PDF. We have to select text on line wise.
I am using this code to draw the screen:
System.Drawing.Rectangle rectangle = new System.Drawing.Rectangle((int)Math.Min(_downX, x),
(int)Math.Min(_downY, y),
(int)Math.Abs(_downX - x),
(int)Math.Abs(_downY - y));
System.Drawing.Bitmap myBitmap = new System.Drawing.Bitmap(#"D:\PDF\ScreenDraw\WpfApplication1\WpfApplication1\Image\Capture.PNG");
using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(myBitmap))
{
System.Drawing.Color customColor = System.Drawing.Color.FromArgb(50, System.Drawing.Color.Red);
System.Drawing.SolidBrush shadowBrush = new System.Drawing.SolidBrush(customColor);
g.FillRectangles(shadowBrush, new System.Drawing.Rectangle[] { rectangle });
}
//myBitmap.Save(#"D:\PDF\abc.png");
//bitmapSource = new BitmapImage(new Uri(#"D:\PDF\abc.png", UriKind.Absolute));
using (var memory = new System.IO.MemoryStream())
{
myBitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
memory.Position = 0;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
Img.Source = bitmapImage;
}
How can I select the text with line wise not a Rect wise?
I have to select text as shown in the above image.
What you want is impossible. You have a bitmap and it is not magically aware of the text in it and nothing will change that.
Although it's not that there is nothing you can do about it.
I don't have time to provide complete solution, but I can provide step-by-step instruction how to achieve the best possible solution.
What you can do is:
Text dimensions definition - Create control with grid overlaid on the image with editable X- and Y-axis step and offset. Then you will be able to calibrate the grid with the lines of text (Y). And character width (X).
Something like that should do (I think you will get the general idea):
public int XGridStep
{
get { return (int)base.GetValue(XGridStepProperty); }
set
{
base.SetValue(XGridStepProperty, value);
RepaintGrid();
}
}
public static readonly DependencyProperty XGridStepProperty = DependencyProperty.Register("XGridStepProperty", typeof(int), typeof(PlanLayout), new PropertyMetadata(100));
public int XGridOffset
{
get { return (int)base.GetValue(XGridOffsetProperty); }
set
{
base.SetValue(XGridOffsetProperty, value);
RepaintGrid();
}
}
public static readonly DependencyProperty XGridOffsetProperty = DependencyProperty.Register("XGridOffsetProperty", typeof(int), typeof(PlanLayout), new PropertyMetadata(0));
public bool XGridVisible
{
get { return (bool)base.GetValue(XGridVisibleProperty); }
set
{
base.SetValue(XGridVisibleProperty, value);
RepaintGrid();
}
}
public static readonly DependencyProperty XGridVisibleProperty = DependencyProperty.Register("XGridVisibleProperty", typeof(bool), typeof(PlanLayout), new PropertyMetadata(false));
public int YGridStep
{
get { return (int)base.GetValue(YGridStepProperty); }
set
{
base.SetValue(YGridStepProperty, value);
RepaintGrid();
}
}
public static readonly DependencyProperty YGridStepProperty = DependencyProperty.Register("YGridStepProperty", typeof(int), typeof(PlanLayout), new PropertyMetadata(100));
public int YGridOffset
{
get { return (int)base.GetValue(YGridOffsetProperty); }
set
{
base.SetValue(YGridOffsetProperty, value);
RepaintGrid();
}
}
public static readonly DependencyProperty YGridOffsetProperty = DependencyProperty.Register("YGridOffsetProperty", typeof(int), typeof(PlanLayout), new PropertyMetadata(0));
public bool YGridVisible
{
get { return (bool)base.GetValue(YGridVisibleProperty); }
set
{
base.SetValue(YGridVisibleProperty, value);
RepaintGrid();
}
}
public static readonly DependencyProperty YGridVisibleProperty = DependencyProperty.Register("YGridVisibleProperty", typeof(bool), typeof(PlanLayout), new PropertyMetadata(false));
private void RepaintGrid()
{
if (!IsEditable)
return;
foreach (Line l in _gridXLines)
content.Children.Remove(l);
_gridXLines.Clear();
if (XGridVisible)
for (int i = XGridOffset; i < content.ActualWidth; i += XGridStep)
{
Line line = new Line();
line.IsHitTestVisible = false;
line.Stroke = Brushes.Black;
line.Y1 = 0;
line.Y2 = content.ActualHeight;
line.X1 = line.X2 = i;
if (Math.Abs(line.X1 - content.ActualWidth) < XGridStep * 0.5 || line.X1 < XGridStep * 0.5)
continue;
_gridXLines.Add(line);
content.Children.Add(line);
Canvas.SetZIndex(line, 0);
}
foreach (Line l in _gridYLines)
content.Children.Remove(l);
_gridYLines.Clear();
if (YGridVisible)
for (int i = YGridOffset; i < content.ActualHeight; i += YGridStep)
{
Line line = new Line();
line.IsHitTestVisible = false;
line.Stroke = Brushes.Black;
line.X1 = 0;
line.X2 = content.ActualWidth;
line.Y1 = line.Y2 = i;
if (Math.Abs(line.Y1 - content.ActualHeight) < YGridStep * 0.5 || line.Y1 < YGridStep * 0.5)
continue;
_gridYLines.Add(line);
content.Children.Add(line);
Canvas.SetZIndex(line, 0);
}
}
Text selection - All you have to do now is add "Snap to grid" ability to your control. Again, just for reference:
private void elementWrapper_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (_mouseHandlingMode != MouseHandlingMode.Dragging)
return;
SelectableElement element = (SelectableElement)sender;
Point curContentPoint = e.GetPosition(content);
//Vector elementDragVector = curContentPoint - _origContentMouseDownPoint;
_origContentMouseDownPoint = curContentPoint;
//double destinationLeft = Canvas.GetLeft(element) + elementDragVector.X;
//double destinationTop = Canvas.GetTop(element) + elementDragVector.Y;
double destinationLeft = curContentPoint.X - element.ActualWidth / 2;
double destinationTop = curContentPoint.Y - element.ActualHeight / 2;
if (SnapToGrid)
{
if (XGridVisible)
{
foreach (Line l in _gridXLines)
l.StrokeThickness = 1;
Line nearest = GetNearestXGridLine((int)curContentPoint.X);
if (Math.Abs(curContentPoint.X - nearest.X1) < XGridStep * 0.2)
{
destinationLeft = nearest.X1 - element.ActualWidth / 2;
nearest.StrokeThickness = 3;
}
}
if (YGridVisible)
{
foreach (Line l in _gridYLines)
l.StrokeThickness = 1;
Line nearest = GetNearestYGridLine((int)curContentPoint.Y);
if (Math.Abs(curContentPoint.Y - nearest.Y1) < YGridStep * 0.2)
{
destinationTop = nearest.Y1 - element.ActualHeight / 2;
nearest.StrokeThickness = 3;
}
}
}
if (destinationLeft < 0)
destinationLeft = 0;
if (destinationLeft > content.ActualWidth - element.ActualWidth)
destinationLeft = content.ActualWidth - element.ActualWidth;
if (destinationTop < 0)
destinationTop = 0;
if (destinationTop > content.ActualHeight - element.ActualHeight)
destinationTop = content.ActualHeight - element.ActualHeight;
Canvas.SetLeft(element, destinationLeft);
Canvas.SetTop(element, destinationTop);
element.ElementContent.Position.X = curContentPoint.X;
element.ElementContent.Position.Y = curContentPoint.Y;
e.Handled = true;
}
private Line GetNearestXGridLine(int xpos)
{
return _gridXLines.OrderBy(gl => Math.Abs((int)gl.X1 - xpos)).First();
}
private Line GetNearestYGridLine(int Ypos)
{
return _gridYLines.OrderBy(gl => Math.Abs((int)gl.Y1 - Ypos)).First();
}
Graphical representation of the selection - Now draw (up to) 3 rectangles: from topleft point of the selection to bottomright point of the relevant text line, topleft point of the next line to bottomright point of the line before the last selected and topleft point of the last line to bottomright of selection
Get text - Get partial text data from these rectangles and join.
I am adding Rectangle from grid cell values that is being entered by user directly in grid rows. When i modify value of specific column say Thickness i.e Height then then it increases Height of selected row rectangle but it doesnt rearrange all rectangle below it exactly after the selected row rectangle.
In xaml.cs
public class MyLayer : INotifyPropertyChanged
{
public string Thickness { get; set; }
public string OffsetRight { get; set; }
public string OffsetLeft { get; set; }
public string Material { get; set; }
public string MaterialPopup { get; set; }
public Rectangle rectangle { get; set; }
public GlassRectangle GlassRectangle { get; set; }
public MaterialLayer()
{
GlassRectangle = new GlassRectangle();
}
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add { }
remove { }
}
}
public class GlassRectangle
{
public Rectangle Rectangle { get; set; }
public double Top = 0;
public GlassRectangle()
{
Rectangle = new Rectangle();
}
}
private void gridInner_CellValueChanged(object sender, DevExpress.Xpf.Grid.CellValueChangedEventArgs e)
{
string cellValue = string.Empty;
MyLayer currentLayer = ((MyLayer)(e.Row));
if (e.Column.HeaderCaption.ToString() == "Thickness")
{
cellValue =(e.Value.ToString());
//there is alredy a rectangle - means this is edit mode
if (currentLayer.rectangle != null)
{
currentLayer.rectangle.Height = Convert.ToDouble(cellValue);
currentLayer.rectangle.Stroke = new SolidColorBrush(Color.FromRgb(0, 255, 0));
}
//else this is insert mode
else
{
currentLayer.rectangle = CreateRectangle(cellValue);
}
}
}
protected Rectangle CreateRectangle(string cellval)
{
Rectangle newrect = new Rectangle();
newrect.Stroke = Brushes.Red;
newrect.StrokeThickness = 1;
if (cellval.ToString().Contains("."))
{
newrect.Height = Convert.ToDouble(cellval) * 100;
}
else
{
newrect.Height = Convert.ToDouble(cellval);
}
newrect.Width = width;
Canvas.SetLeft(newrect, 100);
double canvasTop = 0.0;
if (canvasboard.Children.Count > 0)
{
var lastChildIndex = canvasboard.Children.Count - 1;
var lastChild = canvasboard.Children[lastChildIndex] as FrameworkElement;
if (lastChild != null)
//lastChild.Height-1: so that it come extactly on existing if set to +1 it comes below first rectangle
canvasTop = Canvas.GetTop(lastChild) + lastChild.Height - 1;
}
Canvas.SetTop(newrect, canvasTop);
val = val + 1;
newrect.Tag = val;
canvasboard.Children.Add(newrect);
//rectangle = rect;
foreach (UIElement ui in canvasboard.Children)
{
if (ui.GetType() == typeof(Rectangle))
{
itemstoremove.Add(ui);
}
}
return newrect;
}
NEW EVENT Method:
private void gridMaterialInner_CellValueChanged(object sender, DevExpress.Xpf.Grid.CellValueChangedEventArgs e)
{
string cellValue = string.Empty;
string cellOldValue = string.Empty;
MyLayer currentLayer = ((MyLayer)(e.Row));
if (e.Column.HeaderCaption.ToString() == "Thickness")
{
//current cell value
cellValue =(e.Value.ToString());// GetRowCellValue(e.RowHandle, gridMaterialInner.Columns["LastName"]).ToString();
//there is alredy a rectangle - means this is edit mode
double currentheight = 0.0;
double oldht = 0.0;
// old cell value
if (e.OldValue != null)
{
cellOldValue = (e.OldValue.ToString());
}
if (currentLayer.rectangle != null)
{
if (cellValue.ToString().Contains("."))
{
currentheight = Convert.ToDouble(cellValue) * 100;
}
else
{
currentheight = Convert.ToDouble(cellValue) * 100;
}
if (cellOldValue.ToString().Contains("."))
{
oldht = Convert.ToDouble(cellOldValue) * 100;
}
else if(cellOldValue!=string.Empty)
{
oldht = Convert.ToDouble(cellOldValue) * 100;
}
currentLayer.rectangle.Height = currentheight;
currentLayer.rectangle.Stroke = new SolidColorBrush(Color.FromRgb(0, 255, 0));
//Refresh();
//Get the index of selected row
int layerIndex = materialBindlist.IndexOf(currentLayer);
for(int i = layerIndex; i < materialBindlist.Count-1; i++)
{
//set the top position of all other rectangles that are below selected rectangle/row
//(Current-Old)+Top
Canvas.SetTop(materialBindlist[i + 1].rectangle, (currentheight - oldht) + materialBindlist[i + 1].GlassRectangle.Top);
//Canvas.SetTop(materialBindlist[i].rectangle, (currentheight - oldht) + materialBindlist[i + 1].GlassRectangle.Top);
}
}
//else this is insert mode
else
{
//MaterialLayer object
currentLayer.rectangle = CreateRectangle(cellValue);
//store Top & Rectangle object in GlassRectangle class which is referenced in MaterialLayer class
currentLayer.GlassRectangle.Rectangle = currentLayer.rectangle;
currentLayer.GlassRectangle.Top = canvasTop;
}
}
}
This create rectangle one after other like Stacked item on canvas. But when i modify the value of Thickness column which is Height of Rectangle it reflects on canvas but the Other Rectangle below must appear after the Changed Height of current rectangle.
Note: I cant use WrapPanel in my application. Just to modify existing code using Canvas.
Help Appreciated!
Modified For Loop in CellChange Event:
int layerIndex = materialBindlist.IndexOf(currentLayer);
for(int i = layerIndex; i < materialBindlist.Count-1; i++)
{
//set the top position of all other rectangles that are below selected rectangle/row
//(Current-Old)+Top
double top=Convert.ToDouble((currentHeight - oldHeight) + materialBindlist[i + 1].GlassRectangle.Top);
Canvas.SetTop(materialBindlist[i + 1].rectangle,top);
materialBindlist[i + 1].GlassRectangle.Top = top;
}
What you're looking for can be done even with a Canvas however you should really consider using something like an ItemsControl for this.
Solution when forced to use Canvas:
private void Refresh() {
for (int i = 1; i < canvasboard.Children.Count; ++i) {
var currentElement = canvasboard.Children[i] as FrameworkElement;
var previousElement = canvasboard.Children[i - 1] as FrameworkElement;
if (currentElement == null || previousElement == null)
return;
var requiredTop = Canvas.GetTop(previousElement) + previousElement.Height - 1;
if (Math.Abs(Canvas.GetTop(currentElement) - requiredTop) > 0.0)
Canvas.SetTop(currentElement, requiredTop);
}
}
Now call this function "after" you change the size of an existing element in the Canvas and it will re-position the elements accordingly to suit the new dimension. In your code, it would be called from the gridInner_CellValueChanged(...) function after you set the new height in "edit" mode.
What you should try to do:
If your able to persuade whoever you need to and get to use something like an ItemsControl, this will be so much simpler.
say a rough example:
xaml could be:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
with Items declared as public ObservableCollection<Rectangle> Items { get; set; } in code.
Now your Add() function could just be:
private void Add() {
var rect = new Rectangle {
Stroke = Brushes.Red,
StrokeThickness = 1,
Height = Convert.ToDouble(txtheight.Text),
Width = 100
};
Items.Add(rect);
}
and as for updates when your editing existing control's that would be automatic in this case. There is no hard-coded positioning as the Layout container takes care of all that mess for you.
You can ofcourse switch the Items collection type to your own custom control type MyLayer and with it implementing INPC, changes would still continue to be automatic. You'd have to define a DataTemplate now to get your Item to be rendered but that's like 3 lines of work in just xaml.
You can also just work of the Items property directly when needing to tweak an exisiting control than having to reference the ItemsControl in code-behind. Binding's should take care of the updates to the view automatically.
Modified for loop in Cell Change Event:
int layerIndex = materialBindlist.IndexOf(currentLayer);
for(int i = layerIndex; i < materialBindlist.Count-1; i++)
{
//set the top position of all other rectangles that are below selected rectangle/row
//(Current-Old)+Top
double top=Convert.ToDouble((currentHeight - oldHeight) + materialBindlist[i + 1].GlassRectangle.Top);
Canvas.SetTop(materialBindlist[i + 1].rectangle,top);
materialBindlist[i + 1].GlassRectangle.Top = top;
}
it works now..!Thanks to all!