I am new to blazor and need to create a dynamic treeview where nodes can be created and deleted. All this data is saved in database. After pulling the data from database what kind of object I need to create to bind to Mudtreeview?
If only first level nodes are rendered initially, how can I load the children on node click?
is there any example somewhere? All the sample code on mudblazor site is with static data.
Adding and removeing items is easy if you bind the treeview to a collection in MudBlazor. I made a little demo for you to can play around online: https://try.mudblazor.com/snippet/cuwlvFQcfJTzHuki
Here is the full code of the example:
<MudButton OnClick="AddItems" Variant="Variant.Filled">Add items</MudButton>
<MudButton OnClick="DeleteItems" Variant="Variant.Filled">Delete items</MudButton>
<MudPaper Width="300px" Elevation="0">
<MudTreeView Items="TreeItems" MultiSelection="true" #bind-ActivatedValue="ActivatedValue" #bind-SelectedValues="SelectedValues">
<ItemTemplate>
<MudTreeViewItem #bind-Expanded="#context.IsExpanded" Items="#context.TreeItems" Value="#context"
Icon="#context.Icon" Text="#context.Title" EndText="#context.Number?.ToString()" EndTextTypo="#Typo.caption" />
</ItemTemplate>
</MudTreeView>
</MudPaper>
<div style="width: 100%">
<MudText Typo="#Typo.subtitle1">Activated item: #(ActivatedValue?.Title)</MudText>
<MudText Typo="#Typo.subtitle1">Sum of selected items: #GetSelectedSum()</MudText>
</div>
#code {
private TreeItemData ActivatedValue { get; set; }
private HashSet<TreeItemData> SelectedValues { get; set; }
private HashSet<TreeItemData> TreeItems { get; set; } = new HashSet<TreeItemData>();
public class TreeItemData
{
public string Title { get; set; }
public string Icon { get; set; }
public int? Number { get; set; }
public bool IsExpanded { get; set; }
public HashSet<TreeItemData> TreeItems { get; set; }
public TreeItemData(string title, string icon, int? number = null)
{
Title = title;
Icon = icon;
Number = number;
}
public override bool Equals(object x) {
var other = x as TreeItemData;
if (other==null)
return false;
return other.Title==Title;
}
public override int GetHashCode() {
return Title.GetHashCode();
}
}
protected override void OnInitialized()
{
TreeItems.Add(new TreeItemData("All Mail", Icons.Filled.Email));
TreeItems.Add(new TreeItemData("Trash", Icons.Filled.Delete));
TreeItems.Add(new TreeItemData("Categories", Icons.Filled.Label)
{
IsExpanded = true,
TreeItems = new HashSet<TreeItemData>()
{
new TreeItemData("Social", Icons.Filled.Group, 90),
new TreeItemData("Updates", Icons.Filled.Info, 2294),
new TreeItemData("Forums", Icons.Filled.QuestionAnswer, 3566),
new TreeItemData("Promotions", Icons.Filled.LocalOffer, 733)
}
});
TreeItems.Add(new TreeItemData("History", Icons.Filled.Label));
}
public int GetSelectedSum()
{
return SelectedValues?.Sum(i => i.Number ?? 0) ?? 0;
}
private int i=0;
public void AddItems() {
TreeItems.Add(new TreeItemData("Added Item " + (i++), Icons.Filled.Coronavirus));
}
public void DeleteItems() {
var item=TreeItems.FirstOrDefault(x=>x.Title.StartsWith("Added Item"));
TreeItems.Remove(item);
}
}
Related
I'm using a Telerik grid that is showing rows of data generated from a database. Each database item has a time slot associated with it (by the hour). I have a TelerikDropDownList that adds filtering options to the grid (select an hour). What I'm trying to do is only show the data associated with the hour selected from the dropdown in the grid. I'm using WebAssembly btw.
Here is the grid component:
<ShowSelect />
<TelerikGrid Data="_showItem" Height="80%"
Pageable="true" Sortable="true" Groupable="true"
FilterMode="Telerik.Blazor.GridFilterMode.FilterMenu"
Resizable="true" Reorderable="true" >
<GridColumns>
<GridColumn Field="#(nameof(ShowItem.planned_sequence))" Title="SEQ" />
<GridColumn Field="#(nameof(ShowItem.matrix_id))" Title="Matrix ID" />
<GridColumn Field="#(nameof(ShowItem.item_id))" Title="Item ID" />
<GridColumn Field="#(nameof(ShowItem.item_desc))" Title="Description" />
<GridColumn Field="#(nameof(ShowItem.item_type_id))" Title="Item Type ID" />
<GridColumn Field="#(nameof(ShowItem.planned_selling_price))" Title="Planned Price" />
<GridColumn Field="#(nameof(ShowItem.planned_availabe_qty))" Title="Available Qty" />
<GridColumn Field="#(nameof(ShowItem.planned_minutes))" Title="P Mins" />
</GridColumns>
</TelerikGrid>
#code{
public ShowItem[] _showItem;
[Inject] HttpClient HttpClient { get; set; }
[Inject] public AppData ShowData { get; set; }
protected override async Task OnInitializedAsync()
{
_showItem = await HttpClient.GetJsonAsync<ShowItem[]>("API call...");
_showItem = _showItem.Where(i => i.time_slot_id == ShowData.SelectedShow).ToArray();
}
}
My ShowSelect component:
<TelerikDropDownList Value="ShowData.SelectedShow" Data="#Shows"
TextField="ShowName" ValueField="ShowId"
ValueChanged="#((int s) => ShowSelected(s))"></TelerikDropDownList>
#code {
public List<Show> Shows { get; set; } = new List<Show>();
public SelectedShow SelectedShow { get; set; } = new SelectedShow();
[Inject] public AppData ShowData { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
var time = DateTime.UtcNow.AddHours(-5); //Fix for no WebAssembly time zone option (Eastern Standard Time)
for(int i = 15; i > 0; i--)
{
var hour = time.AddHours(-i);
Shows.Add(new Show { ShowId = hour.Hour, ShowName = $"{Convert.ToChar(int.Parse(hour.ToString("HH")) + 65)} - {hour.ToString("hh tt").ToLower()} {hour.ToString("MM/dd/yy")}" });
}
Shows.Add(new Show { ShowId = time.Hour, ShowName = $"{Convert.ToChar(int.Parse(time.ToString("HH")) + 65)} - {time.ToString("hh tt").ToLower()} {time.ToString("MM/dd/yy")}" });
for(int i = 1; i < 15; i++)
{
var hour = time.AddHours(i);
Shows.Add(new Show { ShowId = hour.Hour, ShowName = $"{Convert.ToChar(int.Parse(hour.ToString("HH")) + 65)} - {hour.ToString("hh tt").ToLower()} {hour.ToString("MM/dd/yy")}" });
}
}
public void ShowSelected(int showId)
{
ShowData.SelectedShow = showId;
Show Show = Shows.Where(s => s.ShowId == showId).First();
SelectedShow.ShowId = Show.ShowId;
SelectedShow.ShowName = Show.ShowName;
}
}
My AppData service:
public class AppData
{
public int SelectedShow { get; set; } = DateTime.UtcNow.AddHours(-5).Hour;
}
And my Models:
public class ShowItem
{
public int network_id { get; set; }
public DateTime spt_date_id { get; set; }
public int time_slot_id { get; set; }
public string show_num { get; set; }
public int planned_sequence { get; set; }
public int item_id { get; set; }
public int matrix_id { get; set; }
public int item_type_id { get; set; }
public int planned_selling_price { get; set; }
public int planned_availabe_qty { get; set; }
public int planned_minutes { get; set; }
public string item_desc { get; set; }
}
public class Show
{
public int ShowId { get; set; }
public string ShowName { get; set; }
}
public class SelectedShow
{
public int ShowId { get; set; }
public string ShowName { get; set; }
}
The problem I'm having is the grid is not changing when selecting a different time slot from the dropdown. Anyone know where I'm going wrong here?
I will assume that the grid component gets the following value correctly set [Inject] public AppData ShowData { get; set; } after the dropdown change.
The issue is that in the grid component there is no event handler or any code that will update the grid data.
The OnInitializedAsync event will fire once when the grid component is added to the page for the first time and that's it - it won't be called again, so your service won't be called again to get new grid data.
The way I would personally handle this is to expose an event from the ShowSelect component that I can consume in the grid component in order to call my service again.
With this, I won't really need the AppState if that's all it contains.
I would also add a parameter to my API so the server will do my filtering and send me back only the relevant data.
EDIT: Because of the comments, I made an example of exposing the event, here it is (I cut some corners in the data binding to make it shorter, but I hope it still illustrates the point).
First, the ShowSelect component
<TelerikDropDownList Value="#ShowId" Data="#Shows"
ValueChanged="#((int s) => ShowSelected(s))"></TelerikDropDownList>
#code {
List<int> Shows { get; set; } = new List<int> { 1, 2, 3 };
[Parameter]
public int ShowId { get; set; }
[Parameter]
public EventCallback<int> OnShowIdChanged { get; set; }
async Task ShowSelected(int showId)
{
ShowId = showId;
await OnShowIdChanged.InvokeAsync(ShowId);
}
}
Then, consuming that event in the main component with the grid:
<ShowSelect ShowId="#ShowId" OnShowIdChanged="#ShowIdChangedHandler" />
<TelerikGrid Data="#MyData" Height="400px" Pageable="true">
<GridColumns>
<GridColumn Field="#(nameof(SampleData.Id))" Width="120px" />
<GridColumn Field="#(nameof(SampleData.Name))" Title="Employee Name" Groupable="false" />
<GridColumn Field="#(nameof(SampleData.GenerationDate))" Title="Benchmark - data generated at" />
<GridColumn Field="#(nameof(SampleData.ShowId))" Title="Show ID - see the dropdown" />
</GridColumns>
</TelerikGrid>
#code {
public List<SampleData> MyData { get; set; }
int ShowId { get; set; } // you may not even want this parameter here, but it helps with keeping the dropdown in sync with the main page
protected override async Task OnInitializedAsync()
{
ShowId = 2;//maybe you fetch that from a service too
MyData = await GetDataFromService(ShowId);
}
async Task ShowIdChangedHandler(int showId)
{
ShowId = showId;
MyData = await GetDataFromService(ShowId);
}
async Task<List<SampleData>> GetDataFromService(int showId)
{
await Task.Delay(500);
//simulate service here
var data = Enumerable.Range(1, 30).Select(x => new SampleData
{
Id = x,
Name = "name " + x,
GenerationDate = DateTime.Now,
ShowId = showId
}).ToList();
return await Task.FromResult(data);
}
public class SampleData
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime GenerationDate { get; set; }
public int ShowId { get; set; }
}
}
I have these two classes:
public class LeadPerformanceItem
{
public string name { get; set; }
public int visitors { get; set; }
public decimal visitorspercentoftotal
{
get
{
// ?
}
}
}
public class LeadPerformanceItemCollection
{
public List<LeadPerformanceItem> items {get;set;}
public int totalvisitors
{
get
{
return items.Sum(x => x.visitors);
}
}
}
Is there anyway my visitorspercentoftotal property could be automatically calculated as items are added and removed from the collection?
public class LeadPerformanceItem
{
public string name { get; set; }
public int Visitors { get; set; }
private int _totalVisitors = 0;
public void UpdateTotalVisitors(int total)
{
this._totalVisitors = total;
}
public decimal Visitorspercentoftotal => _totalVisitors != 0
? Convert.ToDecimal(Math.Round(((double) (Visitors * 100)) / _totalVisitors))
: 0;
}
public class LeadPerformanceItemCollection
{
public List<LeadPerformanceItem> Items { get; set; }
public void AddToItems(LeadPerformanceItem item)
{
Items.Add(item);
var total = Items.Sum(x => x.Visitors);
Items.AsParallel().ForAll(i => i.UpdateTotalVisitors(total));
}
public int totalvisitors
{
get { return Items.Sum(x => x.Visitors); }
}
}
[TestFixture]
public class Class1
{
[Test]
public void Test()
{
var leadPerformanceItemCollection = new LeadPerformanceItemCollection();
leadPerformanceItemCollection.Items=new List<LeadPerformanceItem>();
leadPerformanceItemCollection.AddToItems(new LeadPerformanceItem()
{
name = "test",
Visitors = 10
});
leadPerformanceItemCollection.AddToItems(new LeadPerformanceItem()
{
name = "test2",
Visitors = 25
});
Console.WriteLine(leadPerformanceItemCollection.Items[0].Visitorspercentoftotal);
Console.WriteLine(leadPerformanceItemCollection.Items[1].Visitorspercentoftotal);
}
}
result:
29%
71%
One way would be to inherit from List and hide the Add method and create your own and do the calculation there.
public class LeadPerformanceItemCollection : List<LeadPerformanceItem>
{
public new void Add(LeadPerformanceItem item)
{
//calculate percent of total here
base.Add(item);
}
}
I copied this code from another project and can't figure out why it isn't working. My observable collections are working great binding and updating, but my textboxes aren't changing. I have a button click that lets the user pick a directory (DirectoryBrowse() method) and then assigns that value to the data context's property that is bound to the textbox. PropertyChanged is always null and I can't figure out why! The initial binding works just fine, just note when I change the value in the code-behind. I've been at this entirely too long, but any help would be appreciated!
DataContext class:
[Serializable]
public class Settings : ViewModels.ViewModelEntity
{
public static Settings defaultSettings { get; set; }
private string _ExportDir;
public string ExportDir
{
get { return this._ExportDir; }
set
{
if (this._ExportDir != value)
{
this._ExportDir = value;
this.NotifyPropertyChanged("ExportDir");
}
}
}
private string _LastRunTime;
public string LastRunTime
{
get { return this._LastRunTime; }
set
{
if (this._LastRunTime != value)
{
this._LastRunTime = value;
this.NotifyPropertyChanged("LastRunTime");
}
}
}
private string _TSCertPath;
public string TSCertPath
{
get { return this._TSCertPath; }
set
{
if (this._TSCertPath != value)
{
this._TSCertPath = value;
this.NotifyPropertyChanged("TSCertPath");
}
}
}
public ObservableCollection<Map> Brokers { get; set; }
public ObservableCollection<Account> Accounts { get; set; }
public List<Holiday> Holidays { get; set; }
public bool RefreshHolidays { get; set; }
public string ProxyServer { get; set; }
public string ProxyPort { get; set; }
public string ProxyUsername { get; set; }
public string ProxyPassword { get; set; }
public bool TSProd { get; set; }
public string TSTriad { get; set; }
public string TSPassword { get; set; }
public string TSCertPassword { get; set; }
public Settings()
{
this.Brokers = new ObservableCollection<Map>();
this.Accounts = new ObservableCollection<Account>();
}
}
Xaml:
<TextBlock TextWrapping="Wrap" Text="File Export Path*"/>
<TextBox TextWrapping="Wrap" Text="{Binding Path=ExportDir, Mode=TwoWay}" />
<Button x:Name="btnBrowseExportDir" Content="..." Click="btnBrowseExportDir_Click"/>
Code-behind:
public MainWindow()
{
InitializeComponent();
Settings.Initialize();
this.DataContext = Settings.defaultSettings;
string[] args = Environment.GetCommandLineArgs();
if (args.Contains("create"))
{
this.Close();
}
}
private string DirectoryBrowse()
{
CommonOpenFileDialog dialog = new CommonOpenFileDialog();
dialog.IsFolderPicker = true;
CommonFileDialogResult result = dialog.ShowDialog();
if (result.ToString().ToUpper() == "OK")
{
if (!Directory.Exists(dialog.FileNames.First()))
{
this.lblStatus.Text = "Invalid directory selected";
return string.Empty;
}
else
{
return dialog.FileNames.First();
}
}
else
{
this.lblStatus.Text = "Invalid directory selected";
return string.Empty;
}
}
private void btnBrowseExportDir_Click(object sender, RoutedEventArgs e)
{
Settings.defaultSettings.ExportDir = DirectoryBrowse();
}
ViewModelEntity:
public class ViewModelEntity
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Settings.defaultSettings is never assigned a value. So the databinding have nothing to work with.
Thoug code for Settings.Initialize() is missing.
#Dave and #Icepickle showed me what I was missing, no implementaiton of INotifyPropertyChanged!
So I'm trying to build a small cookbook application using WPF and MVVM light. I've run into a situation where I'm binding a List from the model to the view model. And it works fine for displaying and removing items, but when adding items I couldn't get the display to update.
I came across ObserableCollections which seemed to be just what I wanted, but I'm not sure I'm using them correctly because it seems wrong to be creating a new OC every time. How am I supposed to be retrieving an observable collection when the model is using a list?
Model(s):
public class Recipe
{
public int Id { get; set; }
public string Title { get; set; }
public List<RecipeIngredient> Ingredients { get; set; }
}
public class RecipeIngredient
{
// ... //
}
ViewModel:
public Recipe SelectedRecipe
{
get
{
return this.selectedRecipe;
}
set
{
this.selectedRecipe = value;
RaisePropertyChanged("SelectedRecipe");
RaisePropertyChanged("RecipeIngredients");
}
}
public ObservableCollection<RecipeIngredient> RecipeIngredients
{
get
{
return new ObservableCollection<RecipeIngredient>(selectedRecipe.Ingredients.ToList());
}
}
public RelayCommand<EventArgs> AddIngredientCommand { get; private set; }
public RelayCommand<string> DeleteIngredientCommand { get; private set; }
private void AddIngredient(EventArgs eventArgs)
{
SelectedRecipe.Ingredients.Add(new RecipeIngredient() { Name = "New Ingredient" });
RaisePropertyChanged("RecipeIngredients");
}
private void DeleteIngredient(string name)
{
SelectedRecipe.Ingredients = SelectedRecipe.Ingredients.Where(i => i.Name != name).ToList();
RaisePropertyChanged("RecipeIngredients");
}
public MainViewModel()
{
DBController db = new DBController();
recipes = db.GetRecipeList();
RecipeSelectionChangedCommand = new RelayCommand<SelectionChangedEventArgs>((args) => RecipeSelectionChanged(args));
SaveRecipeCommand = new RelayCommand<EventArgs>((args) => SaveRecipe(args));
AddIngredientCommand = new RelayCommand<EventArgs>((args) => AddIngredient(args));
DeleteIngredientCommand = new RelayCommand<string>((args) => DeleteIngredient(args));
}
Am I way off track here?
Should have read more carefully. If you're displaying the selected recipe's ingredients in an alternate view, you should be using data binding in the view <ListBox ItemsSource="{Binding SelectedRecipe.Ingredients}"/> You could consider using linq to entities (Entity Framework) for ORM..
public class RecipeVM
{
public RecipeVM(Recipe r)
{
recipe = r;
}
Recipe recipe;
public int Id
{
get
{
return recipe.Id;
}
set
{
PropertyChanged("Id");
recipe.id = value;
}
}
public string Title
{
get
{
return recipe.Title;
}
set
{
PropertyChanged("Title");
recipe.Title = value;
}
}
ObservableCollection<RecipeIngredient> ingredients;
public ObservableCollection<RecipeIngredient> Ingredients
{
get
{
if (ingredients == null)
ingredients = new ObservableCollection<RecipeIngredient>(recipe.Ingredients);
return ingredients;
}
set
{
PropertyChanged("Ingredients");
ingredients = value;
}
}
}
You'll need to modify that a bit if you want to keep the collections in sync though..
I am working with Dual ListBoxes and using two buttons to move data from one listbox to another..
Here following code I have done to move data from one Listbox to another
InstituteInformation.cs
public class InstituteInformation
{
public int Id { get; set; }
public string InstituteName { get; set; }
}
MemberAccessRights.cs
public class MemberAccessRights
{
public int Id { get; set; }
public List<InstituteInformation> AvailableNames { get; set; }
public int[] AvailableSelected { get; set; }
public List<InstituteInformation> RequestedNames { get; set; }
public string[] RequestedSelected { get; set; }
public string SavedRequested { get; set; }
}
//Controller
//
// GET: /MemberDetails/Create
public ActionResult Create()
{
Wrapper1 MD = new Wrapper1();
MD.MAR = new MemberAccessRights{ AvailableNames = getAllInstituteNameList(), RequestedNames = new List<InstituteInformation>() };
return View(MD);
}
//
// POST: /MemberDetails/Create
[HttpPost]
public ActionResult Create(Wrapper1 MD, string add, string remove)
{
try
{
ModelState.Clear();
RestoreSavedState(MD);
if (!string.IsNullOrEmpty(add))
AddNames(MD);
elseif (!string.IsNullOrEmpty(remove))
AddNames(MD);
SaveState(MD);
using (varMDConext = new WrapperDB())
{
MDConext.MBIDBS.Add(MD.MBI);
MDConext.MACDBS.Add(MD.MAC);
MDConext.MARDBS.Add(MD.MAR);
MDConext.MODBS.Add(MD.MO);
}
returnRedirectToAction("Index");
}
catch
{
return View(MD);
}
}
#regionSupportFuncs
void SaveState(Wrapper1 MD)
{
MD.MAR.SavedRequested = string.Join(",", MD.MAR.RequestedNames.Select(p =>p.Id.ToString()).ToArray());
////Available Names = All - Requested
MD.MAR.AvailableNames = getAllInstituteNameList().Except(MD.MAR.RequestedNames).ToList();
}
//RestoreSavedState
void RestoreSavedState(Wrapper1 MD)
{
MD.MAR.RequestedNames = newList<InstituteInformation>();
if (!string.IsNullOrEmpty(MD.MAR.SavedRequested))
{
string[] nameids = MD.MAR.SavedRequested.Split(',');
var name = getAllInstituteNameList().Where(p =>nameids.Contains(p.Id.ToString()));
MD.MAR.RequestedNames.AddRange(name);
}
}
//AddNames
void AddNames(Wrapper1 MD)
{
if (MD.MAR.AvailableSelected != null)
{
var names = getAllInstituteNameList().Where(p =>MD.MAR.AvailableSelected.Contains(p.Id));
MD.MAR.RequestedNames.AddRange(names);
MD.MAR.AvailableSelected = null;
}
}
//RemoveNames
void RemoveNames(Wrapper1 MD)
{
if (MD.MAR.RequestedSelected != null)
{
MD.MAR.RequestedNames.RemoveAll(p =>MD.MAR.RequestedSelected.Contains(p.Id.ToString()));
MD.MAR.RequestedSelected = null;
}
}
#endregion
View
List of Financial Institute
<%:Html.ListBoxFor(model=>model.MAR.AvailableSelected,new MultiSelectList(Model.MAR.AvailableNames,"Id","InstituteName",Model.MAR.AvailableSelected)) %>
<div>
<input id="add" name="add" type="submit" value=">>" />
<br />
<input id="remove" name="remove" type="submit" value="<<" />
</div>
<%:Html.ListBoxFor(m=>m.MAR.RequestedSelected,new MultiSelectList(Model.MAR.RequestedNames,"Id","Name",Model.MAR.RequestedSelected)) %>
But there is the problem is that when I click on add(>>) or remove(<<) button the action is performed on the complete page just like the submit button which save the data from that page to db. Here I wanted to know how to perform the action button after clicking the add(>>) or remove(<<) button.
please help to solve this
The idea was that a form could contain more than one submit button issuing a form post to a different way.