WPF validation run-time generated forms - c#

I'm creating a WPF application which generates a form based on a model to edit it. I use reflection to go through all properties of the model to create inputfields for the properties. The GenerateForm method iterates through the properties and uses the SimpleInputFactory to generate input fields. I want to validate the input of the generated fields, but all validation methods require that you know what you are going to validate (either it's using generics or you have to specify it on the binding in the XAML). I want to validate the input based on attributes in the models. Is there any existing way of doing this? I could just make it myself, but if there is some existing way it would help.
Thanks in advance.
public static Grid GenerateForm(List<object> basisgegevensModels, AddOrEdit addOrEdit)
{
if (basisgegevensModels.Count <= 0)
return null;
Grid formGrid = new Grid();
formGrid.Margin = new Thickness(20,20,20,20);
formGrid.HorizontalAlignment = HorizontalAlignment.Stretch;
AddColumnToGrid(formGrid, GridUnitType.Star, 1);
AddColumnToGrid(formGrid, GridUnitType.Star, 3);
AddColumnToGrid(formGrid, GridUnitType.Star, 1);
AddColumnToGrid(formGrid, GridUnitType.Star, 3);
AddRowToGrid(formGrid, GridUnitType.Auto, 0);
var propertyInfos = new List<PropertyInfo>();
foreach (var propertyInfo in basisgegevensModels[0].GetType().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance))
{
var visibleAttribute = propertyInfo.GetCustomAttributes(typeof(Visible), false).Cast<Visible>().FirstOrDefault();
if (visibleAttribute == null || visibleAttribute.IsVisible)
propertyInfos.Add(propertyInfo);
}
int column = 0;
int row = 0;
foreach (var property in propertyInfos)
{
if (row >= Math.Ceiling((decimal)propertyInfos.Count / 2) && row != 0 && column != 2)
{
column = 2;
row = 0;
}
var displayNameAttribute = basisgegevensModels[0].GetType().GetProperty(property.Name).GetCustomAttributes(typeof(DisplayNameAttribute), false)
.Cast<DisplayNameAttribute>().FirstOrDefault();
string displayName;
if (displayNameAttribute != null)
displayName = displayNameAttribute.DisplayName;
else
displayName = property.Name;
bool isEditAllowed = true;
if (addOrEdit == AddOrEdit.Edit)
{
var editAllowed =
basisgegevensModels[0].GetType()
.GetProperty(property.Name)
.GetCustomAttributes(typeof (EditAllowed), false)
.Cast<EditAllowed>()
.FirstOrDefault();
if (editAllowed != null)
isEditAllowed = editAllowed.IsEditAllowed;
}
//add label for inputfield
TextBlock label = SimpleInputFieldFactory.CreateTextBlock(displayName, column, row);
label.VerticalAlignment = VerticalAlignment.Center;
formGrid.Children.Add(label);
column++;
//add input field
formGrid.Children.Add(SimpleInputFieldFactory.CreateInputField(basisgegevensModels, property, isEditAllowed, column, row, 300, HorizontalAlignment.Left));
column--;
row++;
if (column == 0)
{
AddRowToGrid(formGrid, GridUnitType.Auto, 0);
}
}
return formGrid;
}
SimpleInputFieldFactory Class:
public class SimpleInputFieldFactory
{
public static Control CreateInputField(List<object> basisgegevensModels, PropertyInfo property, bool editAllowed, int column, int row, double inputFieldWidth, HorizontalAlignment inputFieldHorAlignment)
{
Control inputField = null;
var triggers = new List<System.Windows.Interactivity.EventTrigger>();
var multiBinding = new MultiBinding();
multiBinding.NotifyOnSourceUpdated = true;
multiBinding.Mode = BindingMode.TwoWay;
multiBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
foreach (var basisgegevensModel in basisgegevensModels)
{
Binding binding = new Binding(property.Name)
{
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
Source = basisgegevensModel,
Mode = BindingMode.TwoWay
};
multiBinding.Bindings.Add(binding);
}
//add inputfield
if (property.PropertyType == typeof(string) || property.PropertyType == typeof(int))
{
string valueAsString = "";
if (property.GetValue(basisgegevensModels[0]) != null)
valueAsString = property.GetValue(basisgegevensModels[0]).ToString();
inputField = CreateTextBox(valueAsString, column, row);
triggers.Add(new System.Windows.Interactivity.EventTrigger("EditValueChanged"));
}
else if (property.PropertyType == typeof(bool))
{
bool valueAsBool = false;
if (property.GetValue(basisgegevensModels[0]) != null)
valueAsBool = (bool)property.GetValue(basisgegevensModels[0]);
inputField = CreateCheckBox(valueAsBool, column, row);
triggers.Add(new System.Windows.Interactivity.EventTrigger("EditValueChanged"));
}
else if (property.PropertyType.BaseType == typeof(Enum))
{
int valueAsInt = 0;
if (property.GetValue(basisgegevensModels[0]) != null)
valueAsInt = (int)property.GetValue(basisgegevensModels[0]);
inputField = CreateDropDown(property.PropertyType, valueAsInt, column, row);
triggers.Add(new System.Windows.Interactivity.EventTrigger("EditValueChanged"));
((ComboBoxEdit)inputField).SelectedIndex = valueAsInt;
((ComboBoxEdit)inputField).IsTextEditable = false;
}
//add general settings, bindings and triggers
if (inputField != null)
{
inputField.Width = inputFieldWidth;
inputField.HorizontalAlignment = inputFieldHorAlignment;
inputField.Margin = new Thickness(5);
inputField.IsEnabled = editAllowed;
var multiEditAllowedAttribute = property.GetCustomAttributes(typeof(MultiEditAllowed), false)
.Cast<MultiEditAllowed>().FirstOrDefault();
//only add binding and trigger if 1 entity is selected OR multiedit is allowed
if (basisgegevensModels.Count == 1 || multiEditAllowedAttribute == null || multiEditAllowedAttribute.IsMultiEditAllowed)
{
multiBinding.Converter = new MultiEditValueConverter();
inputField.SetBinding(BaseEdit.EditValueProperty, multiBinding);
foreach (var trigger in triggers)
{
var action = new ActionMessage();
action.MethodName = "InputChanged";
trigger.Actions.Add(action);
Interaction.GetTriggers(inputField).Add(trigger);
}
}
else
{
inputField.IsEnabled = false;
}
return inputField;
}
return null;
}
public static List<string> GetEnumList(Type enumType)
{
if (!enumType.IsEnum)
{
return new List<string>();
}
return Enum.GetNames(enumType).ToList();
}
public static TextBlock CreateTextBlock(string text, int column, int row)
{
TextBlock textBlock = new TextBlock();
textBlock.Text = text;
Grid.SetColumn(textBlock, column);
Grid.SetRow(textBlock, row);
return textBlock;
}
private static TextEditBase CreateTextBox(string text, int column, int row)
{
TextEdit textBox = new TextEdit();
textBox.Text = text;
Grid.SetColumn(textBox, column);
Grid.SetRow(textBox, row);
return textBox;
}
private static CheckEdit CreateCheckBox(bool isChecked, int column, int row)
{
CheckEdit checkBox = new CheckEdit();
checkBox.IsChecked = isChecked;
Grid.SetColumn(checkBox, column);
Grid.SetRow(checkBox, row);
return checkBox;
}
private static ComboBoxEdit CreateDropDown(Type enumType, int value, int column, int row)
{
ComboBoxEdit dropDown = new ComboBoxEdit();
foreach (var enumValue in GetEnumList(enumType))
{
dropDown.Items.Add(enumValue);
}
dropDown.SelectedIndex = value;
Grid.SetColumn(dropDown, column);
Grid.SetRow(dropDown, row);
return dropDown;
}
}

Yes, you can use the System.ComponentModel.DataAnnotations for validation.
Documentation for the base namespace : MSDN: System.ComponentModel.DataAnnotations
Examples include RequiredAttribute and RangeAttribute.
Microsoft also provide an excellent example of how to provide validation feedback in realtime to the user in WPF using the ErrorTemplate and Binding in the following example: MSDN: Validation in MVVM using Data Annotations
I've also developed a small framework for my own purposes which incorporates these techniques - basically a base class where you need to decorate your VM with ValidationAttribute derived attributes and use the appropriate Binding and WPF takes care of the rest. GitHub: ValidatingBaseViewModel

Related

WPF Datagrid SelectedIndex issue

I have a Datagrid to which I have added the DataGrid_SelectedCellsChanged event to show RowDetails for that specific cell when clicked.
This is working all fine as it should, but now I have on some that the event fires once, but after that it won't trigger it anymore if I select a cell in the same row. I checked in DataGrid_SelectCellColumnChanged and it won't change the value of the SelectedIndex (dg.SelectedIndex), even so I force it to with static values. Can someone maybe explain that, or have an idea what causes that problem?
Here is some of the code:
Create datagrid:
public static DataGrid AddDataGrid(ViewInfo viewInfo, System.Data.DataSet ds, UIElement parent, bool isReadOnly, bool setHighlight = true, System.Windows.Input.MouseButtonEventHandler clickEvent = null, EventHandler<DataGridCellEditEndingEventArgs> editEndEvent = null, string tagName = "", bool addDataContext = true)
{
try
{
DataTable dt = null;
////Reconfigure the Column Types in the Table for Sorting later
//if (DataGridExtension.CheckDataTableColumnTypeForDate(ds.Tables[0]))
//{
// dt = DataGridExtension.ReConfigureDataTableColumnType(ds.Tables[0]);
//}
//else
//{
// dt = ds.Tables[0];
//}
dt = ds.Tables[0];
DataGrid dg = new DataGrid
{
Name = "DataGrid__" + viewInfo.SprocName,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Margin = (tagName == "SubGrid") ? new Thickness(5, 5, 5, 10) : new Thickness(10, 0, 0, 0),
AutoGenerateColumns = true,
Style = TriggerStyleUtility.CreateStyle<DataGrid>(DataGrid.AlternatingRowBackgroundProperty, Brushes.LightGray),
AlternationCount = 2,
IsReadOnly = isReadOnly,
CanUserAddRows = false,
CanUserDeleteRows = false,
Visibility = Visibility.Visible
};
dg.CellStyle = TriggerStyleUtility.CreateStyle<DataGridCell>(DataGridCell.PaddingProperty, new Thickness(10), dg.CellStyle);
Style st = new Style(typeof(DataGridCell));
st.Setters.Add(new Setter(DataGridCell.PaddingProperty, new Thickness(10)));
//dg.CellStyle = st;
//set events
dg.AutoGeneratingColumn += DataGrid_AutoGeneratingColumn;
dg.Loaded += DataGrid_Loaded;
if (clickEvent != null) dg.MouseDoubleClick += clickEvent;
if (editEndEvent != null) dg.CellEditEnding += editEndEvent;
dg.Sorting += DataGrid_Sorting;
//ColumnCount = ds.Tables[0].Columns.Count;
ColumnCount = dt.Columns.Count;
List<object> list = new List<object>
{
viewInfo
};
if (addDataContext) list.InsertRange(0, new List<object>() { ds } );
dg.DataContext = list;
//dg.ItemsSource = ds.Tables[0].DefaultView;
dg.ItemsSource = dt.DefaultView;
UIElementExtensions.SetHighlight(dg, setHighlight);
//use this to evaluate if it is a SubGrid when Autogenerating Columns for ValueConverter
dg.Tag = tagName;
if (tagName == "SubGrid")
{
dg.MaxHeight = 600d;
dg.SelectedCellsChanged += SubDataGrid_SelectedCellsChanged;
}
//set event for Cell selection changed (triggers when selected row changes)
if (viewInfo.AddPNummerClick) dg.SelectedCellsChanged += DataGrid_SelectedCellsChanged;
//enable and set RowDetails, and events
//if (ds.Tables[0].Rows.Count > 0 && viewInfo.AddRowDetails)
if (dt.Rows.Count > 0 && viewInfo.AddRowDetails)
{
//set event for Cell selection changed (triggers when selected row changes)
if (!viewInfo.AddPNummerClick) dg.SelectedCellsChanged += DataGrid_SelectedCellsChanged;
//add event when cell is clicked (triggers on each cell)
st = new Style(typeof(DataGridCell));
st.Setters.Add(new EventSetter(DataGridCell.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(DataGrid_SelectCellColumnChanged)));
dg.CellStyle = st;
//add event for select row changes (used to bring focus to SubGrid)
st = new Style(typeof(DataGridRow));
st.Setters.Add(new EventSetter(DataGridRow.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(DataGrid_SelectRowDetails)));
dg.RowStyle = st;
//create default Template for RowDetails
DataGridExtension.CreateRowDetailsTemplate(dg);
}
//if no data set dummy item for NoDataTrigger
//if (dt.Rows.Count == 0)
if (ds.Tables[0].Rows.Count == 0)
{
dg.ItemsSource = new List<int> { -1 };
dg.Tag = "empty";
DataGridExtension.AddDataTemplate(dg, "Keine Daten gefunden!");
DataGridExtension.SetNoDataTrigger(dg);
}
//check parent and set appropriate position for DataGrid
if (parent.GetType() == typeof(DockPanel))
{
DockPanel.SetDock(dg, Dock.Top);
}
else
{
Grid.SetRow(dg, 1);
}
//add DatGrid to parent, so Highlights and DataGrid types can be processed (UpdateLayout)
ControlUtility.AddUIElement(parent, dg);
//highlight cells in DataGrid by max and lowest value in column
if (setHighlight) DataGridExtension.StartCellHighlight(dg, viewInfo);
//disable specific grid items (column, row, cell)
if (viewInfo.DisableTableItems != "") DataGridExtension.DisableGridElements(dg, viewInfo);
return dg;
}
catch (Exception ex)
{
FileLogger.HandleError("", false, true, "Error (DataGridUtility, AddDataGrid): " + Environment.NewLine + ex.Message, App._LogFilePath, App._LogFileName);
return null;
}
}
Code for DataGrid_SelectCellColumnChanged:
public static void DataGrid_SelectCellColumnChanged(object sender, MouseButtonEventArgs e)
{
try
{
DataGridCell dc = (DataGridCell)sender;
DataGrid dg = ControlUtility.FindParent<DataGrid>(dc);
UIElementExtensions.SetCurrentColumnIndex(dg, dc.Column.DisplayIndex);
if (dc.Column.DisplayIndex == 0) return;
int col = UIElementExtensions.GetCurrentColumnIndex(dg);
int row = UIElementExtensions.GetCurrentRowIndex(dg);
//change SelectedIndex to current Item Row, needed to trigger the cell changed event
if (dg.SelectedIndex == -1 || UIElementExtensions.GetCurrentColumnIndex(dg) == dg.SelectedIndex || (UIElementExtensions.GetCurrentColumnIndex(dg) != dg.SelectedIndex && UIElementExtensions.GetCurrentColumnIndex(dg) != 0))
{
var currentRowIndex = dg.Items.IndexOf(dg.CurrentItem);
bool changed = (UIElementExtensions.GetCurrentColumnIndex(dg) != dg.SelectedIndex && UIElementExtensions.GetCurrentColumnIndex(dg) != 0) ? true : false;
//if current RowIndex is different, set SelectedIndex to current row
//otherwise reset to -1 and back to current RowIndex, so cell change event can trigger
if (currentRowIndex != -1 && UIElementExtensions.GetCurrentColumnIndex(dg) != dg.SelectedIndex && !changed)
{
dg.SelectedIndex = currentRowIndex;
}
else
{
if (currentRowIndex == 0) return;
int test = UIElementExtensions.GetCurrentColumnIndex(dg);
// here it triggers normally the DataGrid_SelectedCellsChanged event, which usually works
dg.SelectedIndex = col; // UIElementExtensions.GetCurrentColumnIndex(dg);
/* trying to force it, but when debugging the SelectedIndex stays the same even after trying to set it static now,
and SelectedIndex has different value as test. It doesn't take the value????? */
if (dg.SelectedIndex != test) dg.SelectedIndex = 5;
App._CurrentColumnIndex = dg.SelectedIndex;
}
}
}
catch (Exception ex)
{
FileLogger.HandleError("", false, true, "Error (DataGridUtility, DataGrid_SelectedCellsChanged): " + Environment.NewLine + ex.Message, App._LogFilePath, App._LogFileName);
}
}
Class UIElementExtensions:
public class UIElementExtensions
{
//add Property "CurrentColumnIndex" and "CurrentRowIndex" to called UIElement
public static readonly DependencyProperty CurrentColumnIndexProperty = DependencyProperty.RegisterAttached("CurrentColumnIndex", typeof(int), typeof(UIElementExtensions), new FrameworkPropertyMetadata(null));
public static int GetCurrentColumnIndex(UIElement element)
{
if (element == null) throw new ArgumentNullException("element");
return (int)element.GetValue(CurrentColumnIndexProperty);
}
public static void SetCurrentColumnIndex(UIElement element, int value)
{
if (element == null) throw new ArgumentNullException("element");
element.SetValue(CurrentColumnIndexProperty, value);
}
public static readonly DependencyProperty CurrentRowIndexProperty = DependencyProperty.RegisterAttached("CurrentRowIndex", typeof(int), typeof(UIElementExtensions), new FrameworkPropertyMetadata(null));
public static int GetCurrentRowIndex(UIElement element)
{
if (element == null) throw new ArgumentNullException("element");
return (int)element.GetValue(CurrentRowIndexProperty);
}
public static void SetCurrentRowIndex(UIElement element, int value)
{
if (element == null) throw new ArgumentNullException("element");
element.SetValue(CurrentRowIndexProperty, value);
}
//add Property "Highlight" to called UIElement
public static readonly DependencyProperty HighlightProperty = DependencyProperty.RegisterAttached("Highlight", typeof(bool), typeof(UIElementExtensions), new FrameworkPropertyMetadata(null));
public static bool GetHighlight(UIElement element)
{
if (element == null) throw new ArgumentNullException("element");
return (bool)element.GetValue(HighlightProperty);
}
public static void SetHighlight(UIElement element, bool value)
{
if (element == null) throw new ArgumentNullException("element");
element.SetValue(HighlightProperty, value);
}
}
Code DataGrid_SelectedCellsChanged:
public static void DataGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
try
{
DataGrid dg = (DataGrid)sender;
if (dg.SelectedIndex == -1) return;
DataGridRow row = (DataGridRow)(dg.ItemContainerGenerator.ContainerFromItem(dg.SelectedItem));
//return if no row found (happens when sorting by column)
if (row == null) return;
// Getting the ContentPresenter of the row details
DataGridDetailsPresenter presenter = ControlUtility.FindVisualChild<DataGridDetailsPresenter>(row);
Grid parent = ControlUtility.FindVisualChild<Grid>(presenter);
//update presenter Layout to get Grid if presenter is still null
if (parent == null)
{
presenter.UpdateLayout();
parent = ControlUtility.FindVisualChild<Grid>(presenter);
}
DataRowView dataRow = (DataRowView)dg.SelectedItem;
//do stuff
}
}
Edit: To make this a little more clear, this normally works as it should, only on some loaded sprocs it doesn't work. I changed the sprocs on which it doesn't work with code from where it works, but the problem consists, so that can't be the issue. Also if I click another cell in another row, than it works again, but again only for one time. On other sprocs it always works.
Also how can a field (public int SelectedIndex { get; set; }) not be forced to take a value? That's what irritates me the most on it.
FYI: the problem is in the DataGrid_SelectCellColumnChanged event. When SelectedIndex is changed here. This normally triggers the DataGrid_SelectedCellsChanged, when it works.
I appreciate any help on this.

How to bind dynamically created text box?

I'm trying to create pricelist for hotel. I'm having a list of dates, and list of room types in hotel. That lists can contain random number of elements. That is created dynamically, and here is the code:
private void CreateControls() { var colIndex = 0;
var vrsteSoba = _Presenter.VrstaSobeDto.ToArray();
foreach (var bindingItem in vrsteSoba)
{
var lbl = new Label()
{
Width = LABEL_WIDTH,
Height = LABEL_HEIGHT - 5,
Left = 10,
Top = 30 + colIndex * (EDIT_BOX_HEIGHT + SPACE_BETWEEN_CONTROL),
Text = bindingItem
};
_dataPanel.Controls.Add(lbl);
colIndex++;
}
int a = 1;
foreach (var date in _Presenter.CeneTarifa)
{
int y = 0;
var panel = new Panel
{
Height = PANEL_HEIGHT * (vrsteSoba.Length-4),
Width = EDIT_BOX_WIDTH,
Left = a * (EDIT_BOX_WIDTH + SPACE_BETWEEN_CONTROL + 50),
Top = 5
};
_dataPanel.Controls.Add(panel);
var label = new Label
{
Height = EDIT_BOX_HEIGHT,
Location = new Point(0, 10),
Text = date.Datum,
Margin = new Padding(0)
};
panel.Controls.Add(label);
int index = 0;
foreach (var item in date.VrstaSobeCena)
{
var box = new TextBox();
panel.Controls.Add(box);
box.Height = EDIT_BOX_HEIGHT;
box.Width = EDIT_BOX_WIDTH;
box.Location = new Point(0, 30 + y * (EDIT_BOX_HEIGHT + SPACE_BETWEEN_CONTROL));
box.DataBindings.Add(new Binding(nameof(box.Text), date, date.Cena[index].Cena1));
y++;
index++;
}
++a;
}
_dataPanel.AutoScroll = true;
}`
Here is image of that representation.
Now I'm facing a problem of data binding. I need to bind price, two way, for each text box. And I'm stuck.
I have tried to bind it to property name, but then all boxes get same value. If I try to bind it to value via index, I'm getting error
Cannot bind to the property or column 34 on the DataSource. Parameter name: dataMember
Code below is used to fill model that is used in presenter
` private void FillCenePoTarifi() { var sobeArr = VrstaSobeDto.ToArray();
foreach (var datum in Datumi)
{
var dictionary = new Dictionary<string, decimal>();
var cene = new List<Cena>();
foreach (var item in sobeArr)
{
var tarif = _Tarife.Where(x => x.SifTarife == item).FirstOrDefault();
if (tarif != null)
_SastavTarife = HotelierServerLocal.Default.TarifaViewBlo.GetSastaveTarife(tarif.IdTarife);
//proveriti ovu logiku
var cena = _SastavTarife.Where(x => x.Cena1 != 0).Select(c => c.Cena1).FirstOrDefault();
cene.Add(new Cena { Cena1 = cena.ToString()});
dictionary.Add(item, cena);
}
var model = new CenePoTarifi
{
Datum = datum,
VrstaSobeCena = dictionary,
Cena = cene
};
CeneTarifa.Add(model);
}
}`
Finally here are classes that use as model.
` public class CenePoTarifi{
public Dictionary<string, decimal> VrstaSobeCena { get; set; } = new Dictionary<string, decimal>();
public string Datum { get; set; }
private List<Cena> _Cena;
public List<Cena> Cena
{
get => _Cena;
set
{
_Cena = value;
NotifyPropertyChanged("Cena");
}
}
public class Cena :
{
private string _Cena1;
public string Cena1
{
get => _Cena1;
set
{
_Cena = value;
NotifyPropertyChanged("Cena1");
}
}
}`
Does anyone has any suggestions?
Your question is: How to bind dynamically created text box. Here is one tested way for accomplishing that specific task.
First create some textboxes dynamically:
public MainForm()
{
InitializeComponent();
buttonRandom.Click += (sender, e) => generateRandomList();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
List<TextBox> tmp = new List<TextBox>();
for (int column = 1; column < tableLayoutPanel.ColumnCount; column++)
{
for (int row = 1; row < tableLayoutPanel.RowCount; row++)
{
TextBox textBox = new TextBox { Anchor = (AnchorStyles)0xF };
tableLayoutPanel.Controls.Add(textBox, column, row);
tmp.Add(textBox);
textBox.KeyDown += onAnyTextBoxKeyDown;
}
}
_textboxes = tmp.ToArray();
// Generate first dataset
generateRandomList();
}
TextBox[] _textboxes = null;
Then, whenever a new random list is generated, clear any old text and databindings from every TextBox before creating a new data binding for it.
public static Random Rando { get; } = new Random(2);
private void generateRandomList()
{
// Clear ALL the data + bindings for ALL the textboxes.
foreach (var textbox in _textboxes)
{
textbox.Clear();
textbox.DataBindings.Clear();
}
// Generate and create new bindings
int count = Rando.Next(1, 79);
for (int i = 0; i < count; i++)
{
var textbox = _textboxes[i];
VrstaSobeCena vrstaSobeCena =
new VrstaSobeCena{ Sobe = (Sobe)tableLayoutPanel.GetRow(textbox) };
textbox.Tag = vrstaSobeCena;
textbox.DataBindings.Add(
new Binding(
nameof(TextBox.Text),
vrstaSobeCena,
nameof(VrstaSobeCena.Cena),
formattingEnabled: true,
dataSourceUpdateMode: DataSourceUpdateMode.OnPropertyChanged,
null,
"F2"
));
// TO DO
// ADD vrstaSobeCena HERE to the Dictionary<string, decimal> VrstaSobeCena
}
}
The classes shown in your code as binding sources may not bind correctly. One issue I noticed is that the property setters are failing to check whether the value has actually changed before firing the notification. Here's an example of doing that correctly. (For testing purposes I'm showing a Minimal Reproducible Sample "mock" version of a class that implements INotifyPropertyChanged.)
enum Sobe { APP4 = 1, APP5, STUDIO, SUP, APP6, STAND, STDNT, COMSTU, LUXSTU, APP4C, APP4L, APP62, APP6L }
class VrstaSobeCena : INotifyPropertyChanged
{
decimal _price = 100 + (50 * (decimal)Rando.NextDouble());
public decimal Cena
{
get => _price;
set
{
if (!Equals(_price, value))
{
_price = value;
OnPropertyChanged();
}
}
}
Sobe _sobe = 0;
public Sobe Sobe
{
get => _sobe;
set
{
if (!Equals(_sobe, value))
{
_sobe = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Finally, one way to test the two-way binding is to intercept the [Enter] key.
private void onAnyTextBoxKeyDown(object sender, KeyEventArgs e)
{
if ((e.KeyCode == Keys.Enter) && (sender is TextBox textbox))
{
e.SuppressKeyPress = e.Handled = true;
VrstaSobeCena vrstaSobeCena = (VrstaSobeCena)textbox.Tag;
string msg = $"Price for {vrstaSobeCena.Sobe} is {vrstaSobeCena.Cena.ToString("F2")}";
BeginInvoke((MethodInvoker)delegate {MessageBox.Show(msg); });
SelectNextControl(textbox, forward: true, tabStopOnly: true, nested: false, wrap: true);
}
}
Create a List for storing the textbox:
List<TextBox> lstTextbox = new List<TextBox>();
Create a class object that stores the values of "Date" and "room type"
public class RoomTypeDate
{
public string RoomType = "";
public string DateRange = "";
}
Immediately after you created the textbox, assigned the RoomTypeDate info to the tag, add it to lstTextbox.
foreach (var item in date.VrstaSobeCena)
{
var box = new TextBox();
panel.Controls.Add(box);
box.Height = EDIT_BOX_HEIGHT;
box.Width = EDIT_BOX_WIDTH;
box.Location = new Point(0, 30 + y * (EDIT_BOX_HEIGHT + SPACE_BETWEEN_CONTROL));
box.DataBindings.Add(new Binding(nameof(box.Text), date, date.Cena[index].Cena1));
// add the box to the list
lstTextbox.Add(box);
// mark the box with RoomType and DateRange
RoomTypeDate rtd = new RoomTypeDate();
rtd.RoomType = "APP4"; // get the room type
rtd.DateRange = "1.6 - 30.6"; // get date range
box.Tag = rtd;
y++;
index++;
}
Now, to get and set the room price:
public void SetRoomPrice(decimal price, string roomType, string dateRange)
{
foreach (var tb in lstTextBox)
{
var rtd = (RoomTypeDate)tb.Tag;
if(rtd.RoomType == roomType && rtd.DateRange == dateRange)
{
tb.Text = price.ToString();
return;
}
}
}
public decimal GetRoomPrice(string roomType, string dateRange)
{
foreach (var tb in lstTextBox)
{
var rtd = (RoomTypeDate)tb.Tag;
if(rtd.RoomType == roomType && rtd.DateRange == dateRange)
{
return Convert.ToDecimal(rt.Text);
}
}
return 0m;
}
*code untested, might contains bugs

Changing font to DataGridView row is not working in WinForms C#

I have methods which manages datagridview object:
internal static void LoadChannelsInGrid(DataGridView dg, Label noDataLbl, string feedUrl)
{
var response = RssManager.GetRss(feedUrl);
if (response != null)
{
noDataLbl.Visible = false;
dg.Visible = true;
var items = response.OrderByDescending(s => s.PubDateUnix);
dg.DataSource = items.ToArray();
FontifyDataGrid(dg);
}
else
{
noDataLbl.Visible = true;
dg.Visible = false;
}
}
and
private static void FontifyDataGrid(DataGridView dg)
{
for (var i = 0; i < dg.Rows.Count; i++)
{
var item = dg.Rows[i].DataBoundItem as ChannelData;
if (item == null)
{
continue;
}
if (!item.IsLoaded)
{
var actualFont = new Font("Microsoft Sans Serif", 7.8f, FontStyle.Bold);
dg.Rows[i].DefaultCellStyle.Font = actualFont;
}
}
}
and I call :
LoadChannelsInGrid(dataGridView1, noDataLbl, "https://....");
Seems that rows (which model item satisfy IsLoaded value) don't have bold style, still looks regular.
Why ?
If I understood correctly, you need the font to be bold when the IsLoaded property is true.
In that case you need to update your if (!item.IsLoaded) to if (item.IsLoaded)

C# WPF - change cell color dynamiclly

I'm creating from c# Data-grid that contain Data-Table and fill it with data,then I have refresh button that when i'm clicking it i want to see the cells that their value has been change in another color(red for example).
I'm pretty new in WPF so I don't really understand how to do it from the XML and I'm creating the tables from the code so I try to do it from the code too.
tried everything and the cell background is not changing.
Thank's for everyone that will try to help :)
example of the code for creating the DataTable:
string TID =selectedTab.Header.ToString().Split('~')[1]; // (TableID, Lvl)
List<Tuple<string,string>> FieldList = API.getFieldsByTableID(TID); // {(Field_name,size in bits),...}
DataGrid dg = new DataGrid();
DataTable dt = new DataTable();
string[] TableLevel = splitTID(TID); //TableLevel[0]=Table ;TableLevel[1]=Level;
string TableDump = API.GetRegs(TableLevel[0], TableLevel[1]);// Getting debug dump from simics
#endregion
#region *Fields_row*
foreach (var item in FieldList) // First line ,name of fields.
{
dc = new DataColumn(item.Item1, typeof(string));
dt.Columns.Add(dc);
}
#endregion
TableDump = TableDump.Split(':')[1]; // split to get just the dump
int x = 0;
int DumpLen = TableDump.Length; // dump length
int EntrySize = int.Parse(API.GetEntrySize(TID)); // return entry size
int NumOfBytes = round_bits_2_chars_amount(EntrySize);
int count = 0;
while (x < DumpLen)
{
count++;
String str_Entry = BE_to_LE(TableDump.Substring(x, NumOfBytes));
ulong Entry = ulong.Parse(str_Entry, System.Globalization.NumberStyles.HexNumber);
DataRow dr = dt.NewRow();
int row = 0;
dr[row++] = count;
foreach (var item in FieldList)
{
int FieldLen = int.Parse(item.Item2);
ulong Mask =(ulong) ((1 << FieldLen) - 1);
ulong Value = Entry & Mask;
Entry = Entry >> FieldLen;
if (Properties.Settings.Default.IsHexadecimal)
{
dr[row] = "0x" + Value.ToString("X");
}
else
{
dr[row] =Value.ToString();
}
row += 1;
/* if (int.Parse(item.Item2) > DumpLen - x)
{
x = DumpLen + 1;
break;
}
string FieldDump =TableDump.Substring(x,int.Parse(item.Item2));
x +=int.Parse(item.Item2);
dr[row] = long.Parse(FieldDump,System.Globalization.NumberStyles.HexNumber);
row +=1;*/
}
dt.Rows.Add(dr);
x += EntrySize;
}
dg.ItemsSource = new DataView(dt);
selectedTab.Content = dg;
}
}
so after a lot of checking i found the solution.
with the use of :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
namespace Nagasaki
{
public static class Datagrid
{
public static DataGridRow GetSelectedRow(this DataGrid grid)
{
return (DataGridRow)grid.ItemContainerGenerator.ContainerFromItem(grid.SelectedItem);
}
public static DataGridRow GetRow(this DataGrid grid, int index)
{
DataGridRow row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
if (row == null)
{
// May be virtualized, bring into view and try again.
grid.UpdateLayout();
grid.ScrollIntoView(grid.Items[index]);
row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
}
return row;
}
public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int column)
{
if (row != null)
{
DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(row);
if (presenter == null)
{
grid.ScrollIntoView(row, grid.Columns[column]);
presenter = GetVisualChild<DataGridCellsPresenter>(row);
}
DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
return cell;
}
return null;
}
public static T GetVisualChild<T>(Visual parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
public static DataGridCell GetCell(this DataGrid grid, int row, int column)
{
DataGridRow rowContainer = GetRow(grid, row);
return GetCell(grid, rowContainer, column);
}
}
}
I was able to get to specific cell and like this i can change the background color of the cell.

Get the cell value of the third column on button click

I have a button in the first column of my datagrid, and when I click it I'm trying to get the cell value of 3rd column of that row I clicked the button on. So for example I click the button on row 3 of my datagrid I want the cell value of column 3, row 3 which is an int. How can I do that?
XAML for the button:
<Control:DataGrid.Columns>
<Control:DataGridTemplateColumn>
<Control:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Click="ShowHideDetailsClick" Foreground="Black">+</Button>
</DataTemplate>
</Control:DataGridTemplateColumn.CellTemplate>
</Control:DataGridTemplateColumn>
<Control:DataGrid.Columns>
C# to handle click:
private void ShowHideDetailsClick(object sender, RoutedEventArgs e)
{
//want to get cell value here
System.Windows.Controls.Button expandCollapseButton = (System.Windows.Controls.Button)sender;
DependencyObject obj = (DependencyObject)e.OriginalSource;
while (!(obj is ExtendedGrid.Microsoft.Windows.Controls.DataGridRow) && obj != null) obj = VisualTreeHelper.GetParent(obj);
if (obj is ExtendedGrid.Microsoft.Windows.Controls.DataGridRow)
{
if (null != expandCollapseButton && "+" == expandCollapseButton.Content.ToString())
{
(obj as ExtendedGrid.Microsoft.Windows.Controls.DataGridRow).DetailsVisibility = Visibility.Visible;
expandCollapseButton.Content = "-";
}
else
{
(obj as ExtendedGrid.Microsoft.Windows.Controls.DataGridRow).DetailsVisibility = Visibility.Collapsed;
expandCollapseButton.Content = "+";
}
}
}
I use a DataTable to fill my datagrid with data retrieved from a database, see code below:
public DataTable SourceTable
{
get
{
return _sourceTable;
}
set
{
_sourceTable = value;
OnPropertyChanged("SourceTable");
}
}
SourceTable = new DataTable();
SourceTable.Columns.AddRange(new DataColumn[]{
new DataColumn("InputID", typeof(int)),
new DataColumn("TraderID", typeof(string)),
new DataColumn("TradeDate", typeof(DateTime)),
new DataColumn("TradeTime", typeof(TimeSpan)),
new DataColumn("ClientName", typeof(string)),
new DataColumn("CurPair", typeof(string)),
new DataColumn("Amnt", typeof(int)),
new DataColumn("Action", typeof(string)),
new DataColumn("ExecutedRate", typeof(decimal))
});
DataRow rowSource = null;
var OpenTradesQuery = from qa in connection.QuickAnalyzerInputs
where qa.TradeClosedDateTime == null
select new
{
qa.InputID,
qa.TraderID,
qa.ClientTradedDate,
qa.ClientTradedTime,
qa.ClientName,
qa.CurrencyPair,
qa.TradedAmount,
qa.Action,
qa.ExecutedRate
};
foreach (var rowObj in OpenTradesQuery)
{
rowSource = SourceTable.NewRow();
SourceTable.Rows.Add(rowObj.InputID, rowObj.TraderID, rowObj.ClientTradedDate, rowObj.ClientTradedTime, rowObj.ClientName, rowObj.CurrencyPair, rowObj.TradedAmount, rowObj.Action, rowObj.ExecutedRate);
}
And then in my XAML the datagrid is binding SourceTable
Before anything, I'll suggest to create a little Class that will contains information about the Rows of the Database, that will populate the Datagrid. This will help you for some facilities in your code. Also that will help for implementing my answer.
The Class will be something like this:
public class PopulateData
{
public int InputID;
public String TraderID;
public DateTime TradeDate;
public TimeSpan TradeTime;
public string ClientName;
public string CurPair;
public int Amnt;
public string Action;
public decimal ExecutedRate;
public PopulateData(int iId, string tId, DateTime date, TimeSpan tTime, string cName, string curPair, int amnt, string Act, decimal ExRate)
{
InputID = iId;
TraderID = tId;
TradeDate = date;
TradeTime = tTime;
ClientName = cName;
CurPair = curPair;
Amnt = amnt;
Action = Act;
ExecutedRate = ExRate;
}
}
So when you populate the SourceTable, do something like this:
foreach (var rowObj in OpenTradesQuery)
{
rowSource = SourceTable.NewRow();
SourceTable.Rows.Add(new PopulateData( rowObj.InputID, rowObj.TraderID, rowObj.ClientTradedDate, rowObj.ClientTradedTime, rowObj.ClientName, rowObj.CurrencyPair, rowObj.TradedAmount, rowObj.Action, rowObj.ExecutedRate));
}
After you get the row corresponding to your button (the DataGridRow) it's almost done:
You now have to get what is inside the row; a PopulateData actually.
private void ShowHideDetailsClick(object sender, RoutedEventArgs e)
{
System.Windows.Controls.Button expandCollapseButton = (System.Windows.Controls.Button)sender;
DependencyObject obj = (DependencyObject)e.OriginalSource;
while (!(obj is ExtendedGrid.Microsoft.Windows.Controls.DataGridRow) && obj != null) obj = VisualTreeHelper.GetParent(obj);
//Get your entire row view here
if(obj != null && obj is ExtendedGrid.Microsoft.Windows.Controls.DataGridRow)
{
DataGridRow d = obj as DataGridRow; //Convert into DataGridRow
PopulateData st = d.Item as PopulateData; //Get the PopulateData
if (st.TraderID == "what you wants") //Or something else in the Class
{
// some stuff here
}
}
if (obj is ExtendedGrid.Microsoft.Windows.Controls.DataGridRow)
{
if (null != expandCollapseButton && "+" == expandCollapseButton.Content.ToString())
{
(obj as ExtendedGrid.Microsoft.Windows.Controls.DataGridRow).DetailsVisibility = Visibility.Visible;
expandCollapseButton.Content = "-";
}
else
{
(obj as ExtendedGrid.Microsoft.Windows.Controls.DataGridRow).DetailsVisibility = Visibility.Collapsed;
expandCollapseButton.Content = "+";
}
}
}
and that's it , let me know if it works for you.

Categories