I'm building a Blazor server app that has a side navigation component as well as a top navigation component.
I want the side nav's "selected" status to get cleared if the user selects an item from the top nav menu.
In my MainLayout.blazor.cs I have a variable called ResetSelection:
protected bool ResetSelection { get; set; } = false;
I'm passing this from the MainLayout.blazor file into both top and side nav components:
<SideNav AuthUser="#navAuth.User.GetAuthUserModel()" ResetSelection="#ResetSelection" />
<TopNav AuthUser="#Auth.User.GetAuthUserModel()" ResetSelection="#ResetSelection" />
In TopNav.razor.cs I check if a nav item has been selected, and if it has, I set the variable to true:
private void itemSelected(MenuEventArgs<CategoryModel> args)
{
// if item selected set the main nav selected item to null
// breakpoint gets hit-- this method gets fired as expected
ResetSelection = true;
}
In the SideNav.razor.cs component I use an OnParameterSet to check if the param is true, and if so I clear the current selected nav item and reset the variable to false:
protected override void OnParametersSet()
{
base.OnParametersSet();
if (ResetSelection == true)
{
// we never get here!
NavMenu.UnselectAll();
ResetSelection = false;
}
}
I don't ever get the OnParametersSet triggered with a ResetSelection == true condition-- and I don't understand why. I can't seem to make this interaction work between two child components.
Is the parameter passed in being scoped to the local component when it has its value changed in TopNav.razor.cs?
You can apply the Blazor Notification Pattern to your problem.
This approach gets away from the fragile spaghetti plumbing inevitable when you try and create more than simple Parameter/Callback relationships between parent/child components.
Create a simple State object and then cascade it. readonly and IsFixed prevents RenderTree cascades: renders are driven by events.
State Object:
public class MenuState
{
public event EventHandler<EventArgs>? StateChanged;
public void NotifyStateCahnged(object? sender)
=> this.StateChanged?.Invoke(sender, EventArgs.Empty);
}
Your layout.
<CascadingValue Value="_state" IsFixed>
//... layout markup
</CascadingValue>
#code {
private readonly MenuState _state = new();
}
Then wherever you use it:
#implements IDisposable
<h3>TopMenu</h3>
#code {
[CascadingParameter] private MenuState State { get; set; } = new();
protected override void OnInitialized()
=> this.State.StateChanged += this.OnStateChanged;
protected async Task MenuClicked()
{
// do whatever maybe async
// emulate an async action
await Task.Delay(10);
this.State.NotifyStateCahnged(this);
}
private void OnStateChanged(object? sender, EventArgs e)
{
// only need to render if it wasn't me who triggered the event
if (sender != this)
{
// Do whatever
this.StateHasChanged();
}
}
public void Dispose()
=> this.State.StateChanged -= this.OnStateChanged;
}
An alternative to the cascade is to register MenuState as a Scoped Service and then inject it where you need it.
Related
I am trying to call this GetProductStatus() method on a page button click event, but it's loading before the button click. Means when the ViewModel is loading, this is also load automatically.
I would like to declared this VM method "GetProductStatus()" to be called only when a button click event occurs.
ViewModel method:
private async void GetProductStatus()
{
try
{
IsBusy = true;
var status = await ProductStatusService.GetProductStatus(new ProductStatus()
{
StoreCode = s_code,
StartTime = StartDateValue.AddMinutes(time1),
EndTime = StartDateValue.AddMinutes(time2)
});
IsBusy = false;
if (status != null)
{
//Process happens
}
else
{
//Array is Null
}
ProductStatus = status;
}
catch (Exception)
{
ProductStatus = null;
}
}
Here, the method is declared.
public ProductViewModel(INavigation nav, Store store)
{
_Nav = nav;
GetProductStatus();
}
Here, the clicked event.
private async void ProductTypeButton_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new ProductPage(_ViewModel));
}
I would like to declared this VM method "GetProductStatus()" to be
called only when a button click event occurs.
private async void ProductTypeButton_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new ProductPage(_ViewModel));
}
For above code you posted, we can find that the constructor of your viewmodel will be called as soon as you call code new ProductPage(_ViewModel).
So, you can try to remove code GetProductStatus(); in constructor ProductViewModel
public ProductViewModel(INavigation nav, Store store)
{
_Nav = nav;
// remove code here
//GetProductStatus();
}
and add a command in your ViewModel, and bind it to the button in your page.
Please refer to the following code:
public class ProductViewModel
{
public Command LoadDataCommand { get; set; }
public ProductViewModel() {
LoadDataCommand = new Command(loadData);
// remove code here
//GetProductStatus();
}
private void loadData()
{
GetProductStatus(); // add your code here
}
private async void GetProductStatus()
{
// other code
}
}
Note:
1.In this condition, you can also navigate as follows:
private async void ProductTypeButton_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new ProductPage(_ViewModel));
}
2.I don't add parameter to the constructor of ProductViewModel , you can modify above code I posted according to your needs.
Set aside the fact that you are working with views and models. Simply think of them like any other class in c#.
If you need to tell class A "do something under these circumstances`, what are your options?
Pass a parameter in constructor: public ProductViewModel(..., bool doGetProductStatus)..., usage: new ProductViewModel(..., true);
Call a method A.DoSomething(); after you've created it: _ViewModel.DoSomething();
Use MessagingCenter Publish/Subscribe.
I have a blazor popup (avatar), I'm trying to close it with a 'click anywhere to close'
The toggle works, however calling SetCloseAvatar from the main layout page, I get a new instance with the default value of true when CollapseAvatar has already been set to false with the Toggle.
How injecting it in 2 different places does it become a separate instance?
Note: Child component Avatar is a descendent of page, but not a direct
child.
Thanks for looking.
Program.cs
builder.Services.AddSingleton<StateContainerService,StateContainerService>();
StateContainerService
public class StateContainerService
{
public bool CollapseAvatar { get; set; } = true;
public event Action ?OnStateChange;
public void SetToggleAvatar()
{
CollapseAvatar = !CollapseAvatar;
NotifyStateChanged();
}
public void SetCloseAvatar()
{
CollapseAvatar = true;
NotifyStateChanged();
}
private void NotifyStateChanged() => OnStateChange?.Invoke();
}
Avatar Component
private string? AvateCssClass =>
StateContainerService.CollapseAvatar ? "max-h-0" : "max-h-80 p-2";
private void ToggleAvatarView()
{
StateContainerService.SetToggleAvatar();
}
MainLayout.Razor
<div class="page" #onclick="#(()=>CloseAvatar("page"))" >
....stuff
</div>
private void CloseAvatar(string id)
{
StateContainerService.SetCloseAvatar();
}
I have a little problem, could you explain me what is the best practice to load menu items from DB using MVC5 and Entity Framework6. The menu and localization object must be loaded only once, and then just used from some globally available collection. They are not going to change alot after website launch, so I just goung to implement some Update() method and I'll call it when necessary...
Use child actions.
public class FooController : Controller
{
...
[ChildActionOnly]
public ActionResult SiteMenu()
{
// load menu items however you need to
return PartialView("_SiteMenu", menuModel);
}
}
/Views/Shared/_SiteMenu.cshtml
#model Namespace.To.MenuModel
<!-- render your menu here -->
/Views/Shared/_Layout.cshtml
<!-- wherever you want the menu to display -->
#Html.Action("SiteMenu", "Foo")
If you want to cache the result, so the menu doesn't have to be pulled from the DB each request, then you can use the OutputCache attribute on the child action like any other action.
As i have already sad, I have thinked about Global.asax
So there is currently 2 ways how I can do it with Global.asax:
Update using this method is bad idea, use the second one instead
public static ICollection<MenuItem> MenuItems {
get
{
if (Application["MenuItems"] != null)
return (ICollection<MenuItems>)Application["MenuItems"];
else
return new ICollection<MenuItems>();
}
set
{
Application["MenuItems"] = value;
}
}
private void LoadMenuItems()
{
MyContext mc = new MyContext();
this.MenuItems = ms.MenuItems.Include("SubCategories").AsNotTacking().where(x => x.SubCategory == null).ToArray();
}
protected void Application_Start(object sender, EventArgs e)
{
this.MenuItems = LoadMenuItems();
}
And another way (The second one):
public static ICollection<MenuItem> MenuItems { get; set; }
private void LoadMenuItems()
{
MyContext mc = new MyContext();
this.MenuItems = ms.MenuItems.Include("SubCategories").AsNotTacking().where(x => x.SubCategory == null).ToArray();
}
protected void Application_Start(object sender, EventArgs e)
{
this.MenuItems = LoadMenuItems();
}
And same thing for Localization...
Actually i dont know which one is better, need to run some tests.
Almost forgot:
All the things, are contained in the "CustomHttpApplication" class, which is derrived from "HttpApplication" class. and Global.asax shoul be derived from "CustomHttpApplication" class. This way the Global.asax file will be cean and readable, but the business logic will be located one level down...
So the complete code could look like so:
CustomHttpApplication.cs
public class CustomHttpApplication : HttpApplication
{
public static ICollection<MenuItem> MenuItems { get; set; }
private void LoadMenuItems()
{
MyContext mc = new MyContext();
this.MenuItems = ms.MenuItems.Include("SubCategories").AsNotTacking().where(x => x.SubCategory == null).ToArray();
}
}
Global.asax.cs
public class MvcApplication : CustomHttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
MenuItems = this.LoadMenuItems();
}
}
And one more edit, if you/me convert the "LoadMenuItems" method to be a "static" one, than you/me will be able to update MenuItems and/or Localization item collections when needed.
The SelectedIndexChanged event gets fired in my application from a combo box when:
the user chooses a different
item in the combo box, or when:
my own code updates the combo
box's SelectedItem to reflect that
the combo box is now displaying
properties for a different object.
I am interested in the SelectedIndexChanged event for case 1, so that I can update the current object's properties. But in case 2, I do not want the event to fire, because the object's properties have not changed.
An example may help. Let's consider that I have a list box containing a list of people and I have a combo box representing the nationality of the currently selected person in the list. Case 1 could happen if Fred is currently selected in the list, and I use the combo box to change his nationality from English to Welsh. Case 2 could happen if I then select Bob, who is Scottish, in the list. Here, my list update event-handler code sees that Bob is now selected, and updates the combo box so that Scottish is now the selected item. This causes the combo box's SelectedIndexChanged event to be fired to set Bob's nationality to Scottish, even though it already is Scottish.
How can I update my combo box's SelectedItem property without causing the SelectedIndexChanged event to fire? One way would be to unregister the event handler, set SelectedItem, then re-register the event handler, but this seems tedious and error prone. There must be a better way.
I created a class I called SuspendLatch. Offers on a better name are welcome, but it does what you need and you would use it like this:
void Method()
{
using (suspendLatch.GetToken())
{
// Update selected index etc
}
}
void listbox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (suspendLatch.HasOutstandingTokens)
{
return;
}
// Do some work
}
It's not pretty, but it does work, and unlike unregistering events or boolean flags, it supports nested operations a bit like TransactionScope. You keep taking tokens from the latch and it's only when the last token is disposed that the HasOutstandingTokens returns false. Nice and safe. Not threadsafe, though...
Here's the code for SuspendLatch:
public class SuspendLatch
{
private IDictionary<Guid, SuspendLatchToken> tokens = new Dictionary<Guid, SuspendLatchToken>();
public SuspendLatchToken GetToken()
{
SuspendLatchToken token = new SuspendLatchToken(this);
tokens.Add(token.Key, token);
return token;
}
public bool HasOutstandingTokens
{
get { return tokens.Count > 0; }
}
public void CancelToken(SuspendLatchToken token)
{
tokens.Remove(token.Key);
}
public class SuspendLatchToken : IDisposable
{
private bool disposed = false;
private Guid key = Guid.NewGuid();
private SuspendLatch parent;
internal SuspendLatchToken(SuspendLatch parent)
{
this.parent = parent;
}
public Guid Key
{
get { return this.key; }
}
public override bool Equals(object obj)
{
SuspendLatchToken other = obj as SuspendLatchToken;
if (other != null)
{
return Key.Equals(other.Key);
}
else
{
return false;
}
}
public override int GetHashCode()
{
return Key.GetHashCode();
}
public override string ToString()
{
return Key.ToString();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Dispose managed resources.
parent.CancelToken(this);
}
// There are no unmanaged resources to release, but
// if we add them, they need to be released here.
}
disposed = true;
// If it is available, make the call to the
// base class's Dispose(Boolean) method
//base.Dispose(disposing);
}
}
}
I think the best way would be to use a flag variable:
bool updatingCheckbox = false;
void updateCheckBox()
{
updatingCheckBox = true;
checkbox.Checked = true;
updatingCheckBox = false;
}
void checkbox_CheckedChanged( object sender, EventArgs e )
{
if (!updatingCheckBox)
PerformActions()
}
[Edit: Posting only the code is not really clear]
In this case, the event handler wouldn't perform its normal operations when the checkbox is changed through updateCheckBox().
I have always used a boolean flag variable to protect against unwanted event handlers. The TaskVision sample application taught me how to do this.
Your event handler code for all of your events will look like this:
private bool lockEvents;
protected void MyEventHandler(object sender, EventArgs e)
{
if (this.lockEvents)
{
return;
}
this.lockEvents = true;
//Handle your event...
this.lockEvents = false;
}
I let the event fire. But, I set a flag before changing the index and flip it back after. In the event handler, I check if the flag is set and exit the handler if it is.
I think your focus should be on the object and not on the event that's occuring.
Say for example you have the event
void combobox_Changed( object sender, EventArgs e )
{
PerformActions()
}
and PerformActions did something to the effect of
void PerformActions()
{
(listBox.SelectedItem as IPerson).Nationality =
(comboBox.SelectedItem as INationality)
}
then inside the Person you would expect to see something to the effect of
class Person: IPerson
{
INationality Nationality
{
get { return m_nationality; }
set
{
if (m_nationality <> value)
{
m_nationality = value;
this.IsDirty = true;
}
}
}
}
the point here is that you let the object keep track of what is happening to itself, not the UI. This also lets you keep track of dirty flag tracking on your objects, which could be useful for persistence later on.
This also keeps your UI clean and keeps it from getting odd event registration code that will most likely be error prone.
I have finally found a solution to avoid the uncessary event from being fired too many time.
I use a counter and I only hook/unhook the events I want to mask once when it is not needed, and when it is needed again.
The example below shows how I hide the CellValueChanged event of a datagrid.
EventMask valueChangedEventMask;
// In the class constructor
valueChangedEventMask = new EventMask(
() => { dgv.CellValueChanged += new DataGridViewCellEventHandler(dgv_CellValueChanged); },
() => { dgv.CellValueChanged -= new DataGridViewCellEventHandler(dgv_CellValueChanged); }
);
// Use push to hide the event and pop to make it available again. The operation can be nested or be used in the event itself.
void changeCellOperation()
{
valueChangedEventMask.Push();
...
cell.Value = myNewCellValue
...
valueChangedEventMask.Pop();
}
// The class
public class EventMask
{
Action hook;
Action unHook;
int count = 0;
public EventMask(Action hook, Action unHook)
{
this.hook = hook;
this.unHook = unHook;
}
public void Push()
{
count++;
if (count == 1)
unHook();
}
public void Pop()
{
count--;
if (count == 0)
hook();
}
}
Say I have a box that says ENABLED or DISABLED.
How can I make the text vary depending on a state?
public void CheckBox1CheckedChanged(object sender, EventArgs e)
{
if (checkBox1.Checked) {
checkBox1.Text = "Enabled";
}
else {
checkBox1.Text = "Disabled";
}
}
box.Text = (box.Enabled ? "ENABLED" : "DISABLED");
If I understand correctly, you are asking how to have a label or some other bit of UI text automatically update to reflect a "state variable". This is just one way to accomplish what you're describing:
I would do it by having a central state object which implements INotifyPropertyChanging and INotifyPropertyChanged. When your application initializes, attach event handlers to the events those interfaces expose, and one of those event handlers can change the text of the label when property (Foo) changes.
public class State : INotifyPropertyChanging, INotifyPropertyChanged
{
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanging(PropertyChangingEventArgs e)
{
if (this.PropertyChanging != null)
{
this.PropertyChanging(this, e);
}
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, e);
}
}
public bool Foo
{
get
{
return foo;
}
set
{
if (value != foo)
{
this.OnPropertyChanging(new PropertyChangingEventArgs("Foo"));
foo = value;
this.OnPropertyChanged(new PropertyChangedEventArgs("Foo"));
}
}
}
private bool foo = false;
}
protected void HandleStateChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "Foo")
{
box.Text = state.Foo ? "Enabled" : "Disabled";
}
}
What jeffamaphone said, but I should add that any time that state changes you will have to make sure to run that same code. The easiest way to insure this happens is by binding the box.Text property to the state object that you are interested in. That way, any change made to the object is immediately reflected in the text.
This blog post
helped me get started with data binding.... because I love FAQs.
The last few months I have been going with a slightly lighter weight solution than implementing a whole class to manage state. I usually define an enum which indicates the types of states available in the UI, then I have a function that makes changes to the UI, based on the state selected. This approach has been very successful, and not too heavy in terms of the amount of code needed to be written.
If I want to know what states are available in the UI, I can check the values of the enum.
public enum SystemState
{
/// <summary>
/// System is under the control of a remote logging application.
/// </summary>
RemoteMode,
/// <summary>
/// System is not under the control of a remote logging application.
/// </summary>
LocalMode
}
public interface IView
{
void SetState(SystemState state);
}
//method on the UI to modify UI
private void SetState(SystemState state)
{
switch (state)
{
case SystemState.LocalMode:
//for now, just unlock the ui
break;
case SystemState.RemoteMode:
//for now, just lock the ui
break;
default:
throw new Exception("Unknown State requested:" + state);
}
}
//now when you change state, you can take advantage of intellisense and compile time checking:
public void Connect()
{
SetState(SystemState.RemoteMode);
}