dynamically generating controls - c#

I have a foreach loop and I need to create a button that allows the user to get the address of a specific location. The issue is when the page is generated, if you click ANY button, they all display the popover.
#foreach (var schedule in _schedules) {
<BSButton Id="popover1" onclick="onclick1">#schedule.Location.NickName</BSButton>
<BSPopover Target="popover1" IsOpen="#IsOpen1" Placement="Placement.Top">
<BSPopoverHeader>#schedule.Location.Name</BSPopoverHeader>
<BSPopoverBody>#schedule.Location.Address</BSPopoverBody>
</BSPopover>
}
code on top of the same page
#code {
bool IsOpen1 { get; set; }
void onclick1(MouseEventArgs e)
{
IsOpen1 = !IsOpen1;
StateHasChanged();
}
}
I am having trouble figuring out how to generate this type of control. I know the problem is the onclick is the same for all the controls. Even if I dynamically change the name in the onclick="#popoverTextId", how do I dynamically create the code in the #code {}

I'll assum Schedule has an Id. Otherwise, improvise something.
<BSButton #onclick="() => onclick1(schedule.Id)"> ... </BSButton>
.... IsOpen="#(schedule.Id == SelectedId)" ...
void onclick1(int scheduleId)
{
SelectedId = scheduleId;
//StateHasChanged();
}

Related

refer to components that created by loop

I want to create a components by following this steps:
I have a list of items.
I want to loop in this list and create a component like InputNumber.
Add EventCallback to the generic created InputNumber that accept ref of this Inputtext because I want to use this ref to set the focus on this InputNumber.
I have also onblure method that execute some code for me, and I am using the onfocus to return focus to the input after execute this code by onblure
My question How can I get this ref and send it as parameter of EventCallback? The problem here that this components have been generated by loop, so I don't want to create by hand hundred variables to represent ref's.
My concept code like this:
#code{
private void OnFocus(MyInputNumber<double?> obj)
{
if (obj is not null)
{
obj!.Element.Value.FocusAsync();
}
}
}
#foreach(var MyItem in MyList)
{
<EditForm Model="MyItem">
//Some components ..
<label>
Test
<InputNumber #bind-Value="MyItem.MyVal"
#onfocus="#((InputNumber<double?> obj #*wrong*#) =>
OnFocus(obj))"
#onblur=#(() => OnblureHandler(context))
</label>
</EditForm>
}
If you see up the parameter InputNumber<double?> obj, this way is wrong, usually I use #ref=SomeVariable but becasue I created in generic way, I can not do that.
Note:
I don't to adjust my list to be dictionary<MYItemType,InputNumber<double?>>, or create a new class that has InputNumber<double?> as property. I am searching for different way, like go from editcontext to any input has been modified and reset focus on it, I don't know if that possible !
You can add an InputNumber<double?> InputNumberRef { get; set; } property to your model class. Then is the foreach loop bind it to the component reference #ref="MyItem.InputNumberRef" then you can pass it in your callback method #onblur="() => HandleBlur(MyItem.InputNumberRef)".
Here is the demo code that I used. The following code after input onblur event it waits 2 seconds and returns the focus to the input.
#page "/"
#foreach (var item in _items)
{
<EditForm Model="#item">
<InputNumber class="form-control" #ref="#item.InputNumberRef" #bind-Value="#item.Value" #onblur="() => HandleBlur(item.InputNumberRef)" />
</EditForm>
}
#code {
private List<Item> _items = new List<Item>
{
new Item { Value = 10 },
new Item { Value = 30 },
new Item { Value = 20 },
};
private async Task HandleBlur(InputNumber<int> inputNumberRef)
{
if (inputNumberRef.Element.HasValue)
{
await Task.Delay(2000);
await inputNumberRef.Element.Value.FocusAsync();
}
}
public class Item
{
public int Value { get; set; }
public InputNumber<int> InputNumberRef { get; set; }
}
}
Credits to user #enet for suggesting this solution in a different question on stackoverflow.
If your requirement is that you apply some form of complex validation on the content of the input before the user is allowed to leave it, i.e if the handler attached to onBlur fails validation then you want to return focus to the input, then this is how to do that without resorting to dictionaries, ...
I've defined a custom InputText component to demonstrate the principles. You'll need to apply the same principles to any other InputBase component where you want to apply the functionality. The key is defining a delegate Func (which returns a bool) as a parameter which is called when the user tries to leave the control. As everything is contained within the component (a bit of SOLID as pointed out by #BrianParker), we can call the inbuilt Element property to return focus to the component.
#inherits InputText
<input #ref="Element"
#attributes="AdditionalAttributes"
class="#CssClass"
value="#CurrentValue"
#oninput="OnInput"
#onblur="OnBlur" />
#if (validationMessage != string.Empty)
{
<div class="validation-message">
#validationMessage
</div>
}
#code {
private string validationMessage = string.Empty;
[Parameter] public Func<string?, Task<bool>>? BlurValidation { get; set; }
[Parameter] public string ValidationFailMessage { get; set; } = "Failed Validation";
private void OnInput(ChangeEventArgs e)
=> this.CurrentValueAsString = e.Value?.ToString() ?? null;
private async Task OnBlur(FocusEventArgs e)
{
validationMessage = string.Empty;
if (Element is not null && BlurValidation is not null && !await this.BlurValidation.Invoke(this.CurrentValue))
{
await Element.Value.FocusAsync();
validationMessage = ValidationFailMessage;
}
}
}
And a demo page:
#page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
#foreach(var item in model)
{
<EditForm Model=item>
<MyInputText class="form-text" #bind-Value=item.MyCountry BlurValidation=CheckBlur />
</EditForm>
}
#code {
private List<MyData> model = new List<MyData>() { new MyData { MyCountry = "UK" }, new MyData { MyCountry = "Australia" } };
private async Task<bool> CheckBlur(string value)
{
// Emulate some async behaviour to do whatever checking is required
await Task.Delay(100);
// simple test here to demonstrate - I know you could use nornal validation to do this!
return value.Length > 5;
}
public class MyData
{
public string? MyCountry { get; set; }
}
}
I'm not sure I'm happy with the UX using this design, but it's your code.

Blazor, MatBlazor - How to catch the value change of MatSelect component

I have used the MatBlazor framework for my project.
In MatSelect, I want to catch its value onchange event to do some other works.
I have tried some solutions but the onchange event has not fired yet.
<MatSelect Label="Customer" Value="#customer" ValueChanged="OnChangeCustomer">
<MatOptionString Value="-1">All</MatOptionString>
#foreach (var item in customers)
{
<MatOption Value="#item.Id">#item.Name</MatOption>
}
</MatSelect>
The below is my onchange event handler. But it did not fired when select another value in drop down list:
public void OnChangeCustomer(ChangeEventArgs args)
{
if (args.Value.ToString() != "-1")
isAccountDropDownListDisabled = false;
}
Can anyone help me?
Thanks
You could refer the following sample to using the MatSelect control:
<MatSelect Outlined="true" Label="Category" ValueChanged="(string i) => OnChangeCategory(i)">
<MatOptionString Value="-1">All</MatOptionString>
#foreach (var cat in GetCategories())
{
<MatOptionString Value="#cat.Id.ToString()">#cat.Name</MatOptionString>
}
</MatSelect>
<span>#selectedValue</span>
#code
{
public string selectedValue;
protected List<Customer> GetCategories()
{
//return new List<string>() { "AA", "BB" };
return new List<Customer>() {
new Customer(){Id=1001, Name="Tom"},
new Customer(){Id=1002, Name="David"},
new Customer(){Id=1003, Name="Lucy"}
};
}
protected void OnChangeCategory(string value)
{
//do something
selectedValue = "Selected Value: " + value;
}
}
The screenshot as below:
More detail information, check the MatSelect document.
The code from #ZhiLv works well but if you want a pre filled dynamic select value it will become harder.
I spent so many hours trying to get this to work with MatSelectValue with no luck.
https://www.matblazor.com/SelectValue
I ended up using a simple MatSelect with a property calling my onchange event method. This is the only way I got the select list to prefill correctly.
Example with nullable int but you can change to string, guid etc as well.
https://www.matblazor.com/Select#MatSelectGuid
#inject StateContainer StateContainer
<MatSelect Label="Choose threat" #bind-Value="#SelectThreatId" Outlined="true" FullWidth="true">
#foreach (var item in selectThreats)
{
<MatOption TValue="int?" Value="#item.Id">#item.Threat</MatOption>
}
</MatSelect>
#code
{
[Parameter]
public ThreatAndCountermeasureDto ThreatAndCountermeasureDto { get; set; }
List<ThreatDto> selectThreats = new List<ThreatDto>();
ThreatDto selectedThreat = null;
private int? threatId = null;
public int? SelectThreatId
{
get { return threatId; }
set
{
threatId = value;
SelectThreatValueChanged(value);
}
}
private void SelectThreatValueChanged(int? id)
{
selectedThreat = StateContainer.Threats.Single(x => x.Id == id);
}
protected override void OnInitialized()
{
base.OnInitialized();
StateContainer.OnChange += StateHasChanged;
SelectThreatId = ThreatAndCountermeasureDto.Threat.Id;
selectThreats = StateContainer.Threats.ToList();
}
...
Source:
https://github.com/SamProf/MatBlazor/issues/498

Data being overwritten using ObjectListView

Apologies for the bad title, hard to sum up.
So what is happening is I am have a form that will load data from the database:
JobModel jobModel = Data.GetJobList(model)[0];
JobModel contains a field for a list of "parts" also known as a "PartModel"
public class JobModel
{
...
public List<PartModel> parts { get; set; }
...
}
So when a user loads up the form I save the data before they begin data entry by assigning this global JobModel to refer back to in later segments of the code. Also the partsModel is located here as well
public static JobModel previousJobModel = new JobModel();
public static List<PartModel> partModels = new List<PartModel>();
public void LoadFormData(int JobID)
{
...
JobModel jobModel = Data.GetJobList(model)[0];
partModels = jobModel.parts;
previousJobModel = jobModel;
...
}
Now what happens is that during a segment of code, the previousJobModel.parts becomes overwritten when the DELETE section of code is executed
private void olvJobPartList_RightClick(object sender, BrightIdeasSoftware.CellRightClickEventArgs e)
{
PartModel model = (PartModel)e.Model;
if (model != null)
{
selectedModel = model;
menuStripOLV.Show(Cursor.Position);
}
}
private void deletePartToolStripMenuItem_Click(object sender, EventArgs e)
{
//previousJobModel.parts Count = 7
var itemToRemove = partModels.Single(r => r.PartNumber == selectedModel.PartNumber && r.partID ==
selectedModel.partID);
partModels.Remove(itemToRemove);
//previousJobModel.parts Count = 6
populateOLV();
}
Few notes: I did put a couple breakpoints in the "delete" function, before the removal of the part from the list, previousJobModel is normal, after it gets screwed up.
I am also getting back into the swing of things with coding in general so I may be missing something dumb here.
Also changing other fields within the job causes no issues to the previous job model, only deleting a part from the list

C# - WPF Entity Framework - Remove Selected Record from Database

In my C# / WPF application, I have a ListView control, which I populate as follows:
private void Load()
{
DbSet<recordHistory> recordHistory = _db.recordHistories;
var query = from cbHistory in recordHistory
orderby cbHistory.id descending
select new { cbHistory.id, cbHistory.Content, cbHistory.Size, cbHistory.DateAdded };
crRecordHistoryList.ItemsSource = query.ToList();
}
The above works as expected. My ListView control is populated with all the saved records from a SQL database.
When I start debugging the application, it executes as expected. However, when I select one of the ListView items (regardless of which item I select) and click on the Remove button, only the first record gets removed from the database and the ListView control.
Intended behavior is for the selected record to be removed from the database & the listview control...
My Remove method
private void Button_Remove_Click(object sender, RoutedEventArgs e)
{
foreach (var record in _db.recordHistories.Local.ToList())
{
Console.WriteLine("Removing Record Id:" + record.id);
_db.recordHistories.Remove(record);
}
_db.SaveChanges();
this.crRecordHistoryList.Items.Refresh();
this.Load();
}
Furthermore, all subsequent item selection and clicking on the remove button result in nothing being removed from database/listview control)
I have also tried the following (just to get the ID), within the Remove method:
Console.WriteLine("Removing Record Id:" + (crRecordHistoryList.SelectedItem as recordHistory).id);
in which case, I get:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
My recordHistory class (auto generated)
using System;
using System.Collections.Generic;
public partial class recordHistory
{
public int id { get; set; }
public string Content { get; set; }
public string Size { get; set; }
public Nullable<int> DateAdded { get; set; }
}
EDIT: I have figured out why it only removes the first record and then nothing else happens (no matter which item is selected)... it is because instead of getting the record from Local (in my foreach statement), I should simply have the following --- which was my initial attempt, trying to get the ID outputted to Console:
private void Button_Remove_Click(object sender, RoutedEventArgs e)
{
recordHistory testRecord = new recordHistory();
testRecord.id = (recordHistory)crRecordHistoryList.SelectedItem;
_db.recordHistories.Attach(testRecord);
_db.recordHistories.Remove(testRecord);
_db.SaveChanges();
this.crRecordHistoryList.Items.Refresh();
this.Load();
}
However, the following line
testRecord.id = (recordHistory)crRecordHistoryList.SelectedItem;
is throwing an error:
Cannot implicitly convert type 'recordHistory' to 'int'
By the way: the above would work perfectly, if I replace the 2nd line with: testRecord.id = 85; for example.
As such, I have tried changing the aforementioned line to the following, to no avail:
testRecord.id = System.Convert.ToInt32(crRecordHistoryList.SelectedItem);
Any ideas how I can remove the selected record?
Kudos to #pinkfloydx33 for pointing me in the right direction. Per his comment, I ventured onto further-research-rabbit-hole which eventually led to me creating a DTO class and modified my Load and Remove methods as follows--
Load method
private void Load()
{
List<HistoryRecordsDTO> records = (from record in _db.recordHistories
orderby record.id descending
select new HistoryRecordsDTO
{
id = record.id,
Content = record.Content,
Size = record.Size,
DateAdded = record.DateAdded
}).ToList();
crRecordHistoryList.ItemsSource = records;
}
Remove method
private void Button_Remove_Click(object sender, RoutedEventArgs e)
{
recordHistory record = new recordHistory();
record.id = (crRecordHistoryList.SelectedItem as HistoryRecordsDTO).id;
_db.recordHistories.Attach(record);
_db.recordHistories.Remove(record);
_db.SaveChanges();
this.crRecordHistoryList.Items.Refresh();
this.Load();
}
And, my DTO class - HistoryRecordsDTO
public class HistoryRecordsDTO
{
public int id { get; set; }
public string Content { get; set; }
public string Size { get; set; }
public Nullable<int> DateAdded { get; set; }
}
Doing the above solved my problem of removing a selected ListView item.
As being a C#/WPF newbie, I am certain that there are much nicer/optimal/better in general ways to do this... I look forward to other answers and learn from it.

Toggle distinct and actual items in list

FileRecord is the observable collection that is being binded with my wpf datagrid in MVVM model.
I have one checkbox for each column above my datagrid. Checkbox name is "SelectUnique--Columnname--". When I click those checkboxes it should show unique values for the column in my grid.
When I click unique check box for TId, I do below logic
var grpd = FileRecord.GroupBy(item => item.TID).Select(grp => grp.First());
FileRecord= new ObservableCollection<FileData>(grpd); // will refresh the grid.
Then again When I click unique check box for CId, I do below logic
var grpd = FileRecord.GroupBy(item => item.CID).Select(grp => grp.First());
FileRecord= new ObservableCollection<FileData>(grpd);// will refresh the grid.
and so on. In this case, for example, if I do unique selection for all my columns, then again If I want to deselect the checkbox randomly(not in the order I selected unique checkboxes) I would like to undo what I have done for that particular column. For example, if I unselect CID unique check box, then the grid should so proper result.
How to acheive this? Please help.
When I want to filter a collection like this I have a property like this:
public IEnumerable<FileData> FilteredFiles
{
get
{
if (Unique)
{
return Files.GroupBy(item => item.TID).Select(grp => grp.First());
}
else
{
return Files.GroupBy(item => item.CID).Select(grp => grp.First());
}
}
}
public ObservableCollection<FileData> Files
{
get; set;
}
public bool Unique
{
get
{
return unique;
}
set
{
unique = value;
RaisePropertyChanged("FilteredFiles");
}
}
Bind to FilteredFiles and when you add/remove from the collection just call RaisePropertyChanged("FilteredFiles") to notify the UI.
You should have a reference of the original collection somewhere, and do all calculations over that one.
For instance, you could have a single method that gets called whenever a CheckBox is checked or unchecked, and have that method filter/group the original collection.
// Simplified properties
private IEnumerable<FileData> FileRecordCollection;
public ObservableCollection<FileData> FileRecord { get; set; }
// Event handlers for the CheckBoxes
private void TID_CheckBox_Checked(object sender, RoutedEventArgs e)
{
UpdateFileRecord();
}
private void TID_CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
UpdateFileRecord();
}
// etc.
// Method that updates FileRecord
private void UpdateFileRecord()
{
IEnumerable<FileData> groupedCollection = FileRecordCollection;
if (TID_CheckBox.IsChecked)
groupedCollection = groupedCollection.GroupBy(item => item.TID).Select(grp => grp.First());
if (CID_CheckBox.IsChecked)
groupedCollection = groupedCollection.GroupBy(item => item.CID).Select(grp => grp.First());
// etc.
FileRecord = new ObservableCollection<FileData>(groupedCollection);
}
This isn't exactly optimal, but I can't think of something better (performance-wise) right now.

Categories