I have a child component for filtering a search (DropdownFilter) which takes an input of a list of suggestions and a function to update that list.
For some reason DropdownFilter.Suggestions isn't being updated after it is initially set and I don't know how to update it again. Any information about how to update the property after it is initially bound would be great!
DropdownFilter.razor:
<input id="search" #onfocus="SearchFocused" #onblur="SearchUnfocused" #oninput="UpdateSearchText" />
#foreach (var suggestion in Suggestions)
{
<p>#suggestion</p>
}
#code {
[Parameter]
public Action<string> SearchFieldChanged { get; set; }
//[Parameter]
//public RenderFragment<TSuggestion> SuggestionTemplate { get; set; }
[Parameter]
public List<string> Suggestions { get; set; }
private bool searchFocus = false;
private void SearchFocused(FocusEventArgs args) {
searchFocus = true;
//UpdateSearchText();
}
private void SearchUnfocused(FocusEventArgs args) => searchFocus = false;
private void UpdateSearchText(ChangeEventArgs args)
{
SearchFieldChanged.Invoke((string)args.Value);
}
public void Refresh() {
StateHasChanged();
}
}
Index.razor:
#page "/example"
<div class="container-fluid dropdown-holder">
<DropdownFilter #ref="dropdown" Suggestions="#maskResults" SearchFieldChanged="UpdateSearchResults" />
</div>
#code {
DropdownFilter dropdown;
public class MaskResult {
public string name;
}
static readonly string[] allMasks = {
"Electric",
"Water",
"Ground",
"Fire",
"Bug"
};
public List<string> maskResults = allMasks.ToList();
private void UpdateSearchResults(string search)
{
search = search.ToLower();
maskResults = allMasks.Where((mask) =>
{
return mask.ToLower().StartsWith(search);
}).ToList();
dropdown.Refresh();
}
}
I think that you are trying to create a Datalist, please check this answer:"
datalist How to bind selected item to object
If you add a StateHasChanged() call just here it should work:
private void UpdateSearchResults(string search)
{
search = search.ToLower();
maskResults = allMasks.Where((mask) =>
{
return mask.ToLower().StartsWith(search);
}).ToList();
StateHasChanged(); // Add this line
dropdown.Refresh();
}
As I understand, if you update manually a Parameter of a component, there are some cases where Blazor does not get automatically the info that it needs to update its components. So if you call StateHasChanged, it will reevaluate all Parameters of the childreen of the component where you do the call.
I'll let someone correct me if I am wrong.
Thanks and good luck :)
Related
I would like to clear entry text from my ViewModel which is binded there. In the code below I tried it by using a RelayCommand, but it doesn't work.
What i want to accomplish: When clicking button named AddQuestionToQuiz, a function is executed by using Command on the button. The function OnCreateQuizClick(), located in my ViewModel, is triggerd and this function needs to clear my entry text, which i don't get for the moment.
I also tried to use a regular Command instead of using a RelayCommand, but also here it doesn't want to work.
EDIT: UNDERNEATH CODE WORKS FINE - GOT UPDATED
Code is used to clear entry text when clicking on a button from your ViewModel, implementing INotifyPropertyChanged Interface
.xaml - code
<Button x:Name="AddQuestionToQuiz" WidthRequest="200" Command="{Binding CreateQuizCommand}" Style="{StaticResource ButtonStyle}" Text="Add question to quiz"></Button>
ViewModel - code
internal class CreateQuizPageViewModel : INotifyPropertyChanged
{
// Quiz Name Input
public String QuizNameInput { get; set; }
private String quizQuestionInput = "";
public String QuizQuestionInput
{
get { return quizQuestionInput; }
set { quizQuestionInput = value; OnPropertyChanged(); }
}
public RelayCommand CreateQuizCommand { get; set; }
public CreateQuizPageViewModel()
{
CreateQuizCommand = new RelayCommand(OnCreateQuizClick);
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void OnCreateQuizClick()
{
QuizQuestionInput = "";
}
}
EDIT: VIEWMODEL UPDATED
.xaml - code
<Button x:Name="AddQuestionToQuiz" WidthRequest="200" Command="{Binding CreateQuizCommand}" Style="{StaticResource ButtonStyle}" Text="Add question to quiz"></Button>
ViewModel - code
internal class CreateQuizPageViewModel : INotifyPropertyChanged
{
// Quiz Name Input
public String QuizNameInput { get; set; }
private String quizQuestionInput = "";
public String QuizQuestionInput
{
get { return quizQuestionInput; }
set { quizQuestionInput = value; OnPropertyChanged(); }
}
public RelayCommand CreateQuizCommand { get; set; }
public CreateQuizPageViewModel()
{
CreateQuizCommand = new RelayCommand(OnCreateQuizClick);
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void OnCreateQuizClick()
{
QuizQuestionInput = "";
}
}
I'm learning Blazor and ASP.NET and have been learning C# for the last 6 months.
I have made a simple templated component:
#typeparam GenericType
<ul>
#foreach (GenericType item in Items)
{
<li #onclick="(x)=> ItemClicked(item)">FragmentToRender(item)</li>
}
</ul>
#code {
[Parameter] public RenderFragment<GenericType> FragmentToRender { get; set; }
[Parameter] public IReadOnlyList<GenericType> Items { get; set; }
public void ItemClicked(GenericType item)
{
//To figure out...
}
}
And I'm using it in a page component:
<TestComponent GenericType="Thing" Items="ListOfThings">
<FragmentToRender>
<p>#context.Field</p>
</FragmentToRender>
</TestComponent>
#code
{
private List<Thing> ListOfThings =
new List<Thing> {
new Thing("Test"),
new Thing("Test2")
};
public class Thing
{
public readonly string Field;
public Thing(string field) => Field = field;
}
}
When the OnClick event of the li element in the component is triggered, how can I pass the specific instance of the item back to the page component (i.e so a different component can do something with the clicked item like upload it's data somewhere)?
Many Thanks
You should use an EventCallback to pass the data.
#typeparam GenericType
<ul>
#foreach (GenericType item in Items)
{
<li #onclick="(x)=> ItemClicked(item)">FragmentToRender(item)</li>
}
</ul>
#code {
[Parameter] public RenderFragment<GenericType> FragmentToRender { get; set; }
[Parameter] public IReadOnlyList<GenericType> Items { get; set; }
// Added EventCallback parameter
[Parameter] public EventCallback<GenericType> OnClick { get; set; }
public void ItemClicked(GenericType item)
{
// Checking if EventCallback is set
if(OnClick.HasDelegate)
{
// Calling EventCallback
OnClick.InvokeAsync(item);
}
}
}
And then just pass the parameter OnClick to that component to get the item
#* Passing the OnClick parameter *#
<TestComponent GenericType="Thing" Items="ListOfThings" OnClick="#HandleClick">
<FragmentToRender>
<p>#context.Field</p>
</FragmentToRender>
</TestComponent>
#code
{
private void HandleClick(Thing item)
{
// Do what you want with the item
}
private List<Thing> ListOfThings =
new List<Thing> {
new Thing("Test"),
new Thing("Test2")
};
public class Thing
{
public readonly string Field;
public Thing(string field) => Field = field;
}
}
Note: I've made some alteration in your code sample...
Point to note:
Add a # symbol before FragmentToRender(item). It instructs the compiler to treat FragmentToRender(item) as executable code. Otherwise, it is used as the content of the li element.
In the second version of the li element, we place the the event call back in
the body of the lambda expression. If you use this version, comment out the
ItemClicked method.
TemplatedComponent.razor
#typeparam GenericType
<ul>
#foreach (GenericType item in Items)
{
<li #onclick="() => ItemClicked(item)">#FragmentToRender(item)</li>
#*<li #onclick="#(() => SelectedItem.InvokeAsync(item))">#FragmentToRender(item)</li>*#
}
</ul>
#code {
[Parameter] public RenderFragment<GenericType> FragmentToRender { get; set; }
[Parameter] public IReadOnlyList<GenericType> Items { get; set; }
// Define an event call back property.
[Parameter] public EventCallback<GenericType> SelectedItem { get; set; }
public async Task ItemClicked(GenericType item)
{
// Check if the event call back property contains a delegate. It's
// important to understand that the EventCallback type is not a true
// delegate. It is actually a struct that may contain a delegate
if(SelectedItem.HasDelegate)
{
await SelectedItem.InvokeAsync(item);
}
}
}
TestComponent.razor
<TemplatedComponent GenericType="Thing" Items="ListOfThings"
SelectedItem="SelectedItem">
<FragmentToRender>
<p>#context.Field</p>
</FragmentToRender>
</TemplatedComponent>
#code
{
// Define a method that will be called by the event call back 'delegate'. It
// receives a single parameter from the calling delegate.
private async Task SelectedItem(Thing item)
{
Console.WriteLine(item.Field);
await Task.CompletedTask;
}
private List<Thing> ListOfThings =
new List<Thing> {
new Thing("Test"),
new Thing("Test2")
};
public class Thing
{
public readonly string Field;
public Thing(string field) => Field = field;
}
}
Index.razor
#page "/"
<TestComponent/>
Hope this helps...
I have a EditForm with MatBlazor Expansion panels. I am trying to set the individual panels border color if any of the form fields are invalid inside it. I am trying to achieve following..
<MatExpansionPanel Class="#(<bool>ChildElementHasValidationMessage() ? "invalid-red-border": "")">.....</MatExpansionPanel>
I am OK with simple equivalent css solution to find a parent element. Please advice.
Just use Style instead Class to override css:
<MatExpansionPanel Style="#(your expression);"
I answered a related question about MatExpansionPanel: Mat Blazor mat-expansion-panel remove elevation/border
Edited
I wrote my own component to send EditContext on changes. I pasted it below. This is how it works:
<h1>#ShowDemo</h1>
<EditForm Model="#model" OnValidSubmit="#SaveItem">
<DataAnnotationsValidator />
<ValidationSummary />
<MyValidationSneak UpdateDelegate="#( (ctx)=>UpdateUI(ctx) )" />
<InputText id="ItemName" #bind-Value="#model.ItemName" />
<ValidationMessage For="#(() => model.ItemName)" />
<button type="submit">Submit</button>
</EditForm>
#code {
string ShowDemo = "res de res";
protected void UpdateUI(EditContext ctx)
{
var _fieldIdentifier = FieldIdentifier.Create( () => model.ItemName );
ShowDemo = string.Join(", ", ctx.GetValidationMessages(_fieldIdentifier) );
}
ItemModel model = new ItemModel();
private void SaveItem() { }
public class ItemModel
{
[Required]
public string ItemName{ get; set; }
}
}
See it in action at BlazorFiddle:
The MyValidationSneak:
public class MyValidationSneak: ComponentBase, IDisposable
{
[CascadingParameter] EditContext CurrentEditContext { get; set; }
[Parameter] public Action<EditContext> UpdateDelegate { get; set; }
private readonly EventHandler<ValidationStateChangedEventArgs> _validationStateChangedHandler;
public MyValidationSneak()
{
_validationStateChangedHandler = (sender, eventArgs) => GoUpdate();
}
private void GoUpdate() => UpdateDelegate(CurrentEditContext);
private EditContext _previousEditContext;
protected override void OnParametersSet()
{
if (CurrentEditContext == null)
{
throw new InvalidOperationException($"{nameof(ValidationSummary)} requires a cascading parameter " +
$"of type {nameof(EditContext)}. For example, you can use {nameof(ValidationSummary)} inside " +
$"an {nameof(EditForm)}.");
}
if (CurrentEditContext != _previousEditContext)
{
DetachValidationStateChangedListener();
CurrentEditContext.OnValidationStateChanged += _validationStateChangedHandler;
GoUpdate();
_previousEditContext = CurrentEditContext;
}
}
protected virtual void Dispose(bool disposing) {}
void IDisposable.Dispose()
{
DetachValidationStateChangedListener();
this.Dispose(disposing: true);
}
private void DetachValidationStateChangedListener()
{
if (_previousEditContext != null)
{
_previousEditContext.OnValidationStateChanged -= _validationStateChangedHandler;
GoUpdate();
}
}
}
Get code at github
I have the following recyclerview where it contains list of TestViewModel objects. In this object I have age, gender and name properties. I am trying to achieve when user click on a list item, it takes user to detail view where user could able to update and click on the save button, then it updates the selected item properties.
Issue:
The following piece of code in MainViewModel where I receive the message from DetailViewModel works when user enter values in the detail and updating each property,
private void OnMessageReceived(TestMessage obj)
{
_selectedItem.Age = obj.messageTest.Age;
_selectedItem.Name = obj.messageTest.Name;
_selectedItem.Gender = obj.messageTest.Gender;
}
but the following piece of code does not work where I am trying to update the object by itself directly.
private void OnMessageReceived(TestMessage obj)
{
_selectedItem= obj.messageTest;
RaisePropertyChanged(() => SelectedItem);
}
Code Implementation is as follows:
<MvxRecyclerView
android:id="#+id/TestRecyclerView"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
local:MvxBind="ItemsSource TestsViews; ; ItemClick ItemSelected" />
MainViewModel
public MainViewModel SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
ShowViewModel<DetailViewModel>(
new DetailViewModel.Parameter
{
Age = _selectedItem.Age,
Name = _selectedItem.Name,
Gender = _selectedItem.Gender,
});
RaisePropertyChanged(() => SelectedItem);
}
}
public virtual ICommand ItemSelected
{
get
{
return new MvxCommand<TestViewModel>(item =>
{
SelectedItem = item;
});
}
}
private void OnMessageReceived(TestMessage obj)
{
_selectedItem.Age= obj.messageTest.Age;
_selectedItem.Name= obj.messageTest.Name;
_selectedItem.Gender= obj.messageTest.Gender;
}
TestMessage
public class TestMessage : MvxMessage
{
public MainModel messageTest { get; set; }
public TestMessage(object sender, MainModel editTest) : base(sender)
{
messageTest = editTest;
}
}
DetailViewModel
public TestViewModel EditTest
{
get { return _editTest; }
set
{
_editTest = value;
RaisePropertyChanged(() => EditTest);
}
}
public DetailViewModel(IMvxMessenger messenger)
{
_messenger = messenger;
}
public void Save()
{
UpdateValues();
}
public void UpdateValues()
{
var message = new TestMessage(this, _editTest);
_messenger.Publish(message, typeof(TestMessage));
}
public void Init(Parameter param)
{
_editTest = new TestViewModel();
_editTest.Age = param.Age;
_editTest.Name = param.Name;
_editTest.Gender = param.Gender;
public class Parameter
{
public double Age { get; set; }
public int Gender { get; set; }
public string Name { get; set; }
}
DetailView xml
<EditText
style="#style/InputNumbersEditText"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="44dp"
android:gravity="center_vertical|right"
android:hint="00.000"
local:MvxBind="Text EditTest.Age, Converter=Nullable;" />
TestViewModel
public class TestViewModel : BaseViewModel
{
public string? Name { get; set; }
public double? Age { get; set; }
public int? Gender { get; set; }
}
NullableValueConverter
public class NullableValueConverter : MvxValueConverter
{
public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (string.IsNullOrEmpty(value?.ToString()))
{
return parameter;
}
return value;
}
public override object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null || string.IsNullOrEmpty(value.ToString()))
{
return null;
}
return value;
}
}
private void OnMessageReceived(TestMessage obj)
{
_selectedItem= obj.messageTest;
RaisePropertyChanged(() => SelectedItem);
}
This can't work, because your are just changing the reference of _selectedItem to point to another object. But this object is not included in the list that is used to show in the recycler view. You are not updating a object, just a reference! You should definitely have a look at the basics of C# to understand this kind of data structure. E.g. Reference vs. Value Type
Your code is a bit faulty.
You SelectedItem has the type MainViewModel
Your click command gets a item of type TestViewModel
public virtual ICommand ItemSelected
{
get
{
return new MvxCommand<TestViewModel>(item =>
{
SelectedItem = item;
});
}
}
Normally this should work:
private void OnMessageReceived(TestMessage obj)
{
_selectedItem.Age= obj.messageTest.Age;
_selectedItem.Name= obj.messageTest.Name;
_selectedItem.Gender= obj.messageTest.Gender;
}
but with a TestViewModel like
public class TestViewModel : BaseViewModel
{
private string? name;
public string? Name { get{ return name; } set { SetProperty(ref name, value); } }
// same for Age and Gender
}
SetProperty sets the value and calls the OnPropertyChanged event.
Updated Answer
Assigning the _selectedItem a new TestViewModel breaks the reference link it has to the TestViewModel held in the TestsViews data source list. While assigning the individual properties maintains the reference to the orginal TestViewModel in the TestsViews list.
Orginal Answer
As you are updating the backing field, _selectedItem, so when you receive the message event the RaisePropertyChanged event defined in the set of your SelectedItem property will never run. You will have to manually trigger the RaisePropertyChanged in order to trigger the binding update.
Try changing your current method:
private void OnMessageReceived(TestMessage obj)
{
_selectedItem= obj.messageTest;
}
To raise the property changed event:
private void OnMessageReceived(TestMessage obj)
{
_selectedItem = obj.messageTest;
RaisePropertyChanged(() => SelectedItem);
}
I want to update a log file(txt) everytime when methods in a an interface class are called?
Is there any way to do this other than writing code in every method to create log?
Here's my 30 mins. you'll have to implement the logging code somewhere so you have to create another abstraction for your code. thus an abstract class is needed. i think. this is very quick and dirty.
public interface IService<T>
{
List<T> GetAll();
bool Add(T obj);
}
then you'll need the abstract class where you'll need to implement your logging routine
public abstract class Service<T> : IService<T>
{
private void log()
{
/// TODO : do log routine here
}
public bool Add(T obj)
{
try
{
log();
return AddWithLogging(obj);
}
finally
{
log();
}
}
public List<T> GetAll()
{
try
{
log();
return GetAllWithLog();
}
finally
{
log();
}
}
protected abstract List<T> GetAllWithLog();
protected abstract bool AddWithLogging(T obj);
}
as for your concrete classes
public class EmployeeService : Service<Employee>
{
protected override List<Employee> GetAllWithLog()
{
return new List<Employee>() { new Employee() { Id = 0, Name = "test" } };
}
protected override bool AddWithLogging(Employee obj)
{
/// TODO : do add logic here
return true;
}
}
public class CompanyService : Service<Company>
{
protected override List<Company> GetAllWithLog()
{
return new List<Company>() { new Company() { Id = 0, Name = "test" } };
}
protected override bool AddWithLogging(Company obj)
{
/// TODO : do add logic here
return true;
}
}
public class Employee
{
public int Id {get;set;}
public string Name { get; set;}
}
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
}
then on your implementation you can just..
static void Main(string[] args)
{
IService<Employee> employee = new EmployeeService();
List<Employee> employees = employee.GetAll();
foreach (var item in employees)
{
Console.WriteLine(item.Name);
}
IService<Company> company = new CompanyService();
List<Company> companies = company.GetAll();
foreach (var item in companies)
{
Console.WriteLine(item.Name);
}
Console.ReadLine();
}
hope this helps!
I think you would have to use Aspect Oriented Programming to achieve that. Read http://www.sharpcrafters.com/aop.net
I think you meant class (instead of interface)
Two options I can think of:
Implementing INotifyPropertyChanged which is in lines of writing code in every method
or
to adopt on of the AOP frameworks in the article http://www.codeproject.com/KB/cs/AOP_Frameworks_Rating.aspx if that is not a major leap