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;
}
}
}
Related
I have made dependency services on both side and Interface in shared library on Xamarin platform
when i use it in xaml.cs hard coded it works but when i use it in ViewModel using messaging center to set count of notification in Toolbar of application it doesn't works
Does anyone have any kind of idea whats happening or should i do ?
this code works from Xaml.cs page
DependencyService.Get<IToolbarItemBadgeService>().SetBadge(this, ToolbarItems[0], "2", Color.Red, Color.White);
And Below Code doesn't works
From ViewModel
" MessagingCenter.Send(new MessagingCenterModel { }, "NotificationCount", NotificationListCount);
At Xaml.cs Page
MessagingCenter.Subscribe<MessagingCenterModel, string>(this, "NotificationCount", (sender, args) =>
{
DependencyService.Get<IToolbarItemBadgeService>().SetBadge(this, ToolbarItems[0], args, Color.Red, Color.White);
});
InterFaceCode
public interface IToolbarItemBadgeService
{
void SetBadge(Page page, ToolbarItem item, string value, Color backgroundColor, Color textColor);
}
Dependency service
public class ToolbarItemBadgeService : IToolbarItemBadgeService
{
public void SetBadge(Page page, ToolbarItem item, string value, Color backgroundColor, Color textColor)
{
Device.BeginInvokeOnMainThread(() =>
{
var toolbar = CrossCurrentActivity.Current.Activity.FindViewById(Resource.Id.toolbar) as Android.Support.V7.Widget.Toolbar;
if (toolbar != null)
{
if (!string.IsNullOrEmpty(value))
{
var idx = page.ToolbarItems.IndexOf(item);
if (toolbar.Menu.Size() > idx)
{
var menuItem = toolbar.Menu.GetItem(idx);
BadgeDrawable.SetBadgeText(CrossCurrentActivity.Current.Activity, menuItem, value, backgroundColor.ToAndroid(), textColor.ToAndroid());
}
}
}
});
}
}
BadgeDrawable Class
public class BadgeDrawable : Drawable
{
private const string BadgeValueOverflow = "*";
private Paint _badgeBackground;
private Paint _badgeText;
private Rect _textRect = new Rect();
private string _badgeValue = "";
private bool _shouldDraw = true;
Context _context;
public override int Opacity => (int)Format.Unknown;
public BadgeDrawable(Context context, Color backgroundColor, Color textColor)
{
_context = context;
float textSize = context.Resources.GetDimension(Resource.Dimension.textsize_badge_count);
_badgeBackground = new Paint();
_badgeBackground.Color = backgroundColor;
_badgeBackground.AntiAlias = true;
_badgeBackground.SetStyle(Paint.Style.Fill);
_badgeText = new Paint();
_badgeText.Color = textColor;
_badgeText.SetTypeface(Typeface.Default);
_badgeText.TextSize = textSize;
_badgeText.AntiAlias = true;
_badgeText.TextAlign = Paint.Align.Center;
}
public override void Draw(Canvas canvas)
{
if (!_shouldDraw)
{
return;
}
Rect bounds = Bounds;
float width = bounds.Right - bounds.Left;
float height = bounds.Bottom - bounds.Top;
float oneDp = 1 * _context.Resources.DisplayMetrics.Density;
// Position the badge in the top-right quadrant of the icon.
float radius = ((Java.Lang.Math.Max(width, height) / 2)) / 2;
float centerX = (width - radius - 1) + oneDp * 2;
float centerY = radius - 2 * oneDp;
canvas.DrawCircle(centerX, centerY, (int)(radius + oneDp * 5), _badgeBackground);
// Draw badge count message inside the circle.
_badgeText.GetTextBounds(_badgeValue, 0, _badgeValue.Length, _textRect);
float textHeight = _textRect.Bottom - _textRect.Top;
float textY = centerY + (textHeight / 2f);
canvas.DrawText(_badgeValue.Length > 2 ? BadgeValueOverflow : _badgeValue,
centerX, textY, _badgeText);
}
// Sets the text to display. Badge displays a '*' if more than 2 characters
private void SetBadgeText(string text)
{
_badgeValue = text;
// Only draw a badge if the value isn't a zero
_shouldDraw = !text.Equals("0");
InvalidateSelf();
}
public override void SetAlpha(int alpha)
{
// do nothing
}
public override void SetColorFilter(ColorFilter cf)
{
// do nothing
}
public static void SetBadgeCount(Context context, IMenuItem item, int count, Color backgroundColor, Color textColor)
{
SetBadgeText(context, item, $"{count}", backgroundColor, textColor);
}
public static void SetBadgeText(Context context, IMenuItem item, string text, Color backgroundColor, Color textColor)
{
if (item.Icon == null)
{
return;
}
BadgeDrawable badge = null;
Drawable icon = item.Icon;
if (item.Icon is LayerDrawable)
{
LayerDrawable lDrawable = item.Icon as LayerDrawable;
if (string.IsNullOrEmpty(text) || text == "0")
{
icon = lDrawable.GetDrawable(0);
lDrawable.Dispose();
}
else
{
for (var i = 0; i < lDrawable.NumberOfLayers; i++)
{
if (lDrawable.GetDrawable(i) is BadgeDrawable)
{
badge = lDrawable.GetDrawable(i) as BadgeDrawable;
break;
}
}
if (badge == null)
{
badge = new BadgeDrawable(context, backgroundColor, textColor);
icon = new LayerDrawable(new Drawable[] { item.Icon, badge });
}
}
}
else
{
badge = new BadgeDrawable(context, backgroundColor, textColor);
icon = new LayerDrawable(new Drawable[] { item.Icon, badge });
}
badge?.SetBadgeText(text);
item.SetIcon(icon);
icon.Dispose();
}
}
Firstly , make sure that we had Subscribe the message before we send it . Otherwise the codes in Subscribe will never been called .
In addition , the code in Dependency Service could only set the badge of ToolbarItem in the navigation bar (on tabbed bar it will never work).
If you want to set the badge of tabbed page icon , you could use the plugin Plugin.Badge .
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.
My aim is to create a .dll file dynamically having TextBox,Button which can be used by anyone in a program using Visual C#.
It would get created in Class Library, No WFA tools would get used.
I need help in creating a form which can generate multiple TextBox according to the attributes provided by the user.
1)No of TextBox
2)Location
3)Size etc
Code
CLASS.CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
namespace Forms
{
public class TextForm : Form
{
public TextBox txtBox1;
public TextForm(int a, int b, int c, int d, string e)
{
Form f1 = new Form();
txtBox1 = new TextBox();
txtBox1.Visible = true;
//f1.ActiveControl=txtBox1;
f1.Controls.Add(txtBox1);
txtBox1.Focus();
f1.Visible = true;
txtBox1.Size = new Size(a, b);
txtBox1.Location = new Point(c, d);
txtBox1.Text = (e).ToString();
this.Controls.Add(txtBox1);
txtBox1.Visible = true;
}
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
}
}}
PROGRAM.CS
using System;
using Forms;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
TextForm Box1 = (new TextForm(150, 14, 20, 32, "This is a TextBox 1"));
TextForm Box2 = (new TextForm(180, 34, 40, 52, "This is a TextBox 2"));
}}}
What should be the code?
The problem is that you are creating a Form for each TextBox. This is not what you want, provided that you plan to have forms with multiple text boxes.
I see two possibilities: you either want to create a) a textbox that you can easily add to your form, or b) a form with textboxes.
public class TextInput : Form
{
public TextBox TxtBox {
get; private set;
}
public Control Container {
get; private set;
}
public TextInput(Control c, int a, int b, int c, int d, string e)
{
this.Container = c;
this.TxtBox = new TextBox();
var txtBox1 = this.TxtBox;
txtBox1.Visible = true;
c.Controls.Add(txtBox1);
txtBox1.Focus();
txtBox1.Size = new Size(a, b);
txtBox1.Location = new Point(c, d);
txtBox1.Text = (e).ToString();
txtBox1.Visible = true;
}
}
You'd use this as follows:
var f = new Form();
var txtBox1 = new TextInput( f, 100, 25, 10, 10, "Name" );
var txtBox1 = new TextInput( f, 100, 25, 10, 50, "Age" );
var txtBox1 = new TextInput( f, 100, 25, 10, 100, "Address" );
var txtBox1 = new TextInput( f, 100, 25, 10, 150, "Phone" );
The second possibility is much more interesting, in my opinion. You want to create a special Form that automatically adds text boxes as soon a you call a simple method. I'm going to simplify your code, though. It is not a good idea (at all), to use absolute positioning in your forms.
The following creates a form with text boxes and their labels. The textboxes occupy the whole width of the form. This is achieved by using a TableLayoutPanel in which a Panel subPanel is used for each row.
This subPanel holds a label and a text box.
public class InputForm: Form {
public InputForm()
{
this.Panel = new TableLayoutPanel{ Dock = DockStyle.Fill };
this.textBoxes = new List<TextBox>();
this.Controls.Add( this.Panel );
}
public TextBox AddTextBox(string label)
{
var subPanel = new Panel { Dock = DockStyle.Top };
var lblLabel = new Label { Text = label, Dock = DockStyle.Left };
var tbEdit = new TextBox{ Dock = DockStyle.Fill };
subPanel.Controls.Add( tbEdit );
subPanel.Controls.Add( lblLabel );
this.Panel.Controls.Add( subPanel );
return tbEdit;
}
public TableLayoutPanel Panel {
get; private set;
}
public TextBox[] TextBoxes {
get {
return this.textBoxes.ToArray();
}
}
private List<TextBox> textBoxes;
}
You can use this with the following simple code:
var form = new InputForm();
var tbName = form.AddTextBox( "Name" );
var tbAge = form.AddTextBox( "Age" );
var tbAddress = form.AddTextBox( "Address" );
form.Show();
Application.Run( form );
If you'd like to give a few attributes to the text boxes to be created (colors, font, bold...), then you have two ways. The first one is to add parameters to the AddTextBox() method, though that would not scalate well as long as the number of attributes grows. The alternative is to create a TextBoxAttributes class, which will hold the configuring attributes for a given TextBox.
public class InputForm: Form {
public class TextBoxAttributes {
public TextBoxAttributes() {
this.ForeColor = DefaultForeColor;
this.BackColor = DefaultBackColor;
this.Font = DefaultFont;
}
public Color ForeColor {
get; set;
}
public Color BackColor {
get; set;
}
public Font Font {
get; set;
}
public bool Bold {
get {
return this.Font.Bold;
}
set {
var style = FontStyle.Regular;
if ( value ) {
style = FontStyle.Bold;
}
this.Font = new Font( this.Font, style );
}
}
public bool Italic {
get {
return this.Font.Bold;
}
set {
var style = FontStyle.Regular;
if ( value ) {
style = FontStyle.Italic;
}
this.Font = new Font( this.Font, style );
}
}
public bool Underline {
get {
return this.Font.Bold;
}
set {
var style = FontStyle.Regular;
if ( value ) {
style = FontStyle.Underline;
}
this.Font = new Font( this.Font, style );
}
}
public float FontSize {
get {
return this.Font.Size;
}
set {
this.Font = new Font( this.Font.FontFamily, value );
}
}
}
// ... more things...
public TextBox AddTextBox(string label)
=> this.AddTextBox( label, new TextBoxAttributes() );
public TextBox AddTextBox(string label, TextBoxAttributes attr)
{
var subPanel = new Panel { Dock = DockStyle.Top };
var lblLabel = new Label { Text = label, Dock = DockStyle.Left };
var tbEdit = new TextBox{
Dock = DockStyle.Fill,
ForeColor = attr.ForeColor,
BackColor = attr.BackColor,
Font = attr.Font
};
subPanel.Controls.Add( tbEdit );
subPanel.Controls.Add( lblLabel );
this.Panel.Controls.Add( subPanel );
return tbEdit;
}
// ... more things...
}
The main code would be:
public static void Main()
{
var form = new InputForm();
var tbName = form.AddTextBox( "Name", new InputForm.TextBoxAttributes {
ForeColor = Color.Yellow,
BackColor = Color.Blue
});
var tbAge = form.AddTextBox( "Age", new InputForm.TextBoxAttributes {
ForeColor = Color.Green,
BackColor = Color.Black,
Bold = true
});
var tbAddress = form.AddTextBox( "Address" );
form.Show();
Application.Run( form );
}
Hope this helps.
If you are willing to switch to WPF this will become a lot easier, since you can profit from Autolayout and Bindings.
You could easily switch the Wrappanel for a StackPanel or a DockPanel.
The class will create a View based on the public properties of the handed over object. You might have to add additional Types to the Types-Dictionary. In my case those two where sufficient.
Create a Property of the DynamicControl and Bind to it in XAML.
XAML:
<ContentPresenter Content="{Binding Path=DynView}" />
public ViewModel
{
public UserControl DynView {get; private set};
private ModelType _model;
public ViewModel(ModelType model)
{
_model = model;
DynView = new DynamicControl<ModelType>(_model);
}
}
public class DynamicControl<T> : UserControl
{
static DynamicControl()
{
Types[typeof(bool)] = (binding) =>
{
CheckBox cb = new CheckBox();
cb.SetBinding(CheckBox.IsCheckedProperty,binding);
return cb;
};
Types[typeof(String)] = (binding) =>
{
TextBox tb = new TextBox();
tb.SetBinding(TextBox.TextProperty, binding);
return tb;
};
// add additional Types if necessary
}
private T _model;
public DynamicControl(T model)
{
_model = model;
WrapPanel wp = new WrapPanel();
foreach (PropertyInfo pi in model.GetType().GetProperties())
{
Grid g = new Grid();
g.Margin = new Thickness(5, 5, 25, 5);
g.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
g.ColumnDefinitions.Add(new ColumnDefinition());
g.ColumnDefinitions.Add(new ColumnDefinition());
g.RowDefinitions.Add(new RowDefinition());
TextBlock tb = new TextBlock() { Text = pi.Name };
tb.VerticalAlignment = VerticalAlignment.Center;
Grid.SetColumn(tb, 0);
Grid.SetRow(tb, 0);
g.Children.Add(tb);
System.Windows.FrameworkElement uie = GetUiElement(pi);
uie.Margin = new Thickness(10, 0, 0, 0);
uie.VerticalAlignment = VerticalAlignment.Center;
Grid.SetColumn(uie, 1);
Grid.SetRow(uie, 0);
g.Children.Add(uie);
wp.Children.Add(g);
}
this.Content = wp;
}
private FrameworkElement GetUiElement(PropertyInfo pi)
{
System.Windows.Data.Binding binding = new System.Windows.Data.Binding(pi.Name);
binding.Source = _model;
Func<System.Windows.Data.Binding, FrameworkElement> func;
FrameworkElement uie = null;
if (Types.TryGetValue(pi.PropertyType, out func))
uie = func(binding);
else
uie = Types[typeof(String)](binding);
return uie;
}
private static Dictionary<Type, Func<System.Windows.Data.Binding, FrameworkElement>> Types = new Dictionary<Type, Func<System.Windows.Data.Binding, FrameworkElement>>();
}
I think you're going at this all wrong. Just define your dimensions and text in advance, put them in a data class, and then feed a list of those data classes to your form constructor so it can construct them all on the fly.
The data class:
public class TextboxInfo
{
public Int32 Width { get; set; }
public Int32 Height { get; set; }
public Int32 X { get; set; }
public Int32 Y { get; set; }
public String Text { get; set; }
public TextboxInfo(Int32 w, Int32 h, Int32 x, Int32 y, String text)
{
this.Width = w;
this.Height = h
this.X = x;
this.Y = y
this.Text = text;
}
}
The code to construct the form:
public class TextForm : Form
{
public TextBox[] TextBoxes
{
get { return _textBoxes.ToArray(); }
}
private List<TextBox> _textBoxes;
public TextForm(TextboxInfo[] textboxes, Int32 padX, Int32 padY)
{
_textBoxes = new List<TextBox>();
Int32 reqWidth = 0;
Int32 reqHeight = 0;
foreach (TextboxInfo tbi in textboxes)
{
reqWidth = Math.Max(reqWidth, tbi.X + tbi.Width);
reqHeight = Math.Max(reqHeight, tbi.Y + tbi.Height);
TextBox txtB = new TextBox();
txtB.Size = new Size(tbi.Width, tbi.Height);
txtB.Location = new Point(tbi.X, tbi.Y);
txtB.Text = tbi.Text;
_textBoxes.Add(txtB);
this.Controls.Add(txtB);
}
// You may want to add some kind of OK button at the end here (based on reqHeight)
// and link that to a click listener that closes the form.
// Don't forget to adjust your reqHeight to the added height of that button!
// ...
// Set form to the minimum needed size according to its elements.
this.Size = new Size(reqWidth + padX, reqHeight + padY);
}
}
The calling code:
class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
TextboxInfo[] info = new TextboxInfo[2];
info[0] = new TextboxInfo(150, 14, 20, 32, "This is a TextBox 1");
info[1] = new TextboxInfo(180, 34, 40, 52, "This is a TextBox 2");
TextForm frm = new TextForm(info, 20, 32);
frm.ShowDialog();
// Now you can access the form's text box values through frm.TextBoxes[i].Text
}
}
Mind you, this whole system may seem useful at first, but consider that none of the text boxes have labels on them. Just starting values.
I've made systems for custom data before in a project I created, to generate a custom save options dialog depending on the chosen file type to save to, since each file type needed specific options.
Realistically, you'd create a form with some kind of description at the top and an OK and a Cancel button at the bottom, with a panel in between which has its vertical scrollbar set to enable-when-needed. Then you can dynamically put different custom controls in there to support different data types, like say, a checkbox, a text field, a numeric field, et cetera. They'll automatically be listed vertically in the list simply by keeping track of each control's height to get the next control's Y-offset, and if they'd exceed the form size the panel will make sure you can scroll down.
All you'd give to the form are objects of a data class like the one I showed, but without positioning data. They'd have a type, to figure out what kind of custom control to create, a description text, a default value to set the input control to, and possibly some kind of initialisation value, for example, to limit the range of a numeric value, or, as in the image I showed, values for a dropdown list.
I have a list of doubles and I want to display a label with the values which is fine. But I want the background to have a shading of red if the value is close to the max or blue if it's close to the min, or white if it's close to the median.
public partial class UserControlTest : Window
{
private double Highest;
private double Minimum;
private double Median;
public UserControlTest()
{
InitializeComponent();
DataSource dataSource = new DataSource();
Data dataSet = dataSource.GetData();
Bind(dataSet);
}
private void Bind(Data dataSet)
{
Highest = Convert.ToDouble(dataSet.Values.Max());
Minimum = Convert.ToDouble(dataSet.Values.Min());
Median = ((Highest - Minimum) / 2) + Minimum;
stk1.Children.Add(DisplayLabel(dataSet));
stk1.Children.Add(DisplayMax(dataSet));
stk1.Children.Add(DisplayMin(dataSet));
stk1.Children.Add(DisplayMed(dataSet));
}
private Label DisplayLabel(Data dataSet)
{
var label = new Label()
{
Content = String.Join(" , ", dataSet.Values),
Background = new SolidColorBrush(Colors.AliceBlue)
};
return label;
}
private Label DisplayMax(Data dataSet)
{
var maxlabel = new Label()
{
Background = new SolidColorBrush(Colors.Red)
};
return maxlabel;
}
private Label DisplayMin(Data dataSet)
{
var minlabel = new Label()
{
Background = new SolidColorBrush(Colors.Blue)
};
return minlabel;
}
private Label DisplayMed(Data dataSet)
{
var medlabel = new Label()
{
Content = Median,
Background = new SolidColorBrush(Colors.Pink)
};
return medlabel;
}
}
}
in MVVM i see two ways of doing this.
create a wrapper list with the double value and the color property. you can simply bind both values in wpf.
use a converter for the label background with the double value as input.
I'm trying to make a custom ContentControl that takes on the shape of a Polyogon with rounded corners. For some reason when I set the Clip property on the Control, nothing shows up. Any help is appreciated...
PageHost.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Controls;
namespace DtcInvoicer.Controls
{
public class PageHost:UserControl
{
#region public ImageSource Icon;
public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(ImageSource), typeof(PageHost));
public ImageSource Icon
{
get { return GetValue(IconProperty) as ImageSource; }
set { SetValue(IconProperty, value); }
}
#endregion
#region public string Title;
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(PageHost));
public string Title
{
get { return GetValue(TitleProperty).ToString(); }
set { SetValue(TitleProperty, value); }
}
#endregion
#region public double Radius;
public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof(double), typeof(PageHost));
public double Radius
{
get { return (double)GetValue(RadiusProperty); }
set
{
SetValue(RadiusProperty, value);
DoClip();
}
}
#endregion
public PageHost()
{
Loaded += new RoutedEventHandler(PageHost_Loaded);
SizeChanged += new SizeChangedEventHandler(PageHost_SizeChanged);
}
#region Event Handlers
private void PageHost_Loaded(object sender, RoutedEventArgs e)
{
DoClip();
}
private void PageHost_SizeChanged(object sender, SizeChangedEventArgs e)
{
DoClip();
}
#endregion
private void DoClip()
{
Polygon p = new Polygon()
{
Points = new PointCollection()
{
new Point(0, 0),
new Point(ActualWidth - 30, 0),
new Point(ActualWidth, 30),
new Point(ActualWidth, ActualHeight),
new Point(0, ActualHeight)
}
};
Geometry g1 = new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight), Radius, Radius);
Geometry g2 = p.RenderedGeometry;
// Clip = g1; this works, the control shows up with the rounded corners
// Clip = g2; this does not work, nothing shows up
// this is what I want to do, I want to combine the two geometries
// but this does not work either
Clip = new CombinedGeometry(GeometryCombineMode.Intersect, g1, g2);
}
}
}
HomePage.xaml
<control:PageHost x:Class="DtcInvoicer.Pages.HomePage"
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:control="clr-namespace:DtcInvoicer.Controls"
Width="500" Height="250" Radius="20" Background="Aqua">
</control:PageHost>
Setting the clip to a RenderedGeometry fails in this case because the RenderedGeometry has not yet been actually rendered, and is thus not available. For regular geometries, use this in DoClip:
Dispatcher.BeginInvoke(DispatcherPriority.Background, new ThreadStart(delegate
{
Clip = new CombinedGeometry(GeometryCombineMode.Intersect, g1, g2);
}));
With your RenderedGeometry, you would need to add it to the visual tree somewhere and then use its Loaded event before you set the Clip region, which would be hard. Try using a regular Geometry instead of a RenderedGeometry, with the same points, such as A path geometry. Here's an example of where I draw a triangle using a PathGeometry:
double leftPoint = cellRect.Right - 12;
if (leftPoint < cellRect.Left)
leftPoint = cellRect.Left;
double topPoint = cellRect.Top + (cellRect.Height - 4.0) / 2;
if (topPoint < cellRect.Top)
topPoint = cellRect.Top;
double rightPoint = leftPoint + 7;
if (rightPoint > cellRect.Right)
rightPoint = cellRect.Right;
double bottomPoint = topPoint + 4;
if (bottomPoint > cellRect.Bottom)
bottomPoint = cellRect.Bottom;
double middlePoint = leftPoint + 3;
if (middlePoint > cellRect.Right)
middlePoint = cellRect.Right;
PathFigure figure = new PathFigure();
figure.StartPoint = new Point(middlePoint, bottomPoint);
PathFigureCollection figCollection = new PathFigureCollection();
figCollection.Add(figure);
PathSegmentCollection segCollection = new PathSegmentCollection();
LineSegment topSeg = new LineSegment();
topSeg.Point = new Point(rightPoint, topPoint);
segCollection.Add(topSeg);
LineSegment midRightSeg = new LineSegment();
midRightSeg.Point = new Point(leftPoint, topPoint);
segCollection.Add(midRightSeg);
LineSegment midLeftSeg = new LineSegment();
midLeftSeg.Point = new Point(middlePoint+1, bottomPoint);
segCollection.Add(midLeftSeg);
figure.Segments = segCollection;
PathGeometry geo = new PathGeometry();
geo.Figures = figCollection;