multiple using for editorforModel - asp.net-mvc-4

editorforModel but Now I need several different ones and I dont want to use html helpers onebyone. So I need something like this ;
#model JobTrackingSystem.Areas.Panel.ViewModels.Member.NewMemberModel
{
#Html.EditorForModel()
}
#model JobTrackingSystem.Areas.Panel.ViewModels.Member.MemberDashboardModel
{
#Html.EditorForModel()
}
So I want to keep them in 2 different divs in 1 page but also my controller wont allow using something like this
here is my controller ;
public ActionResult Add(NewMemberModel input, HttpPostedFileBase Resim)
{
if (!ModelState.IsValid)
{
ShowErrorMessage("Hatalı İşlem Yaptınız.");
return RedirectToAction("Index");
}
if (Resim == null)
{
ShowErrorMessage("Lütfen Boş Alan Bırakmayın.");
return RedirectToAction("Index");
}
var epostaKontrol = Db.MyMembers.FirstOrDefault(p => p.Mail == input.Mail);
if (epostaKontrol != null)
{
ShowErrorMessage("E-Mail Adresi Adı Kullanımda.");
return RedirectToAction("Index");
}
string[] folders = new string[] { "Uploads/Member/Orjinal/", "Uploads/Member/Kucuk/" };
string fileExt = Path.GetExtension(Path.GetFileName(Resim.FileName)).ToLower();
string orjName = Guid.NewGuid() + fileExt;
string filePath = Path.Combine(Server.MapPath("~/" + folders[0]), orjName);
string fileThumbPath = Path.Combine(Server.MapPath("~/" + folders[1]), orjName);
if (!(fileExt.Equals(".jpg") || fileExt.Equals(".jpeg") || fileExt.Equals(".png")))
{
ShowErrorMessage("Yalnızca .Jpg .Jpeg ve .Png Uzantılı Dosyalar Yükleyebilirsiniz.");
return RedirectToAction("Index");
}
Resim.SaveAs(filePath);
var thumber = ImageHelper.Thumber(750, filePath, fileThumbPath);
if (!String.IsNullOrWhiteSpace(thumber))
{
ShowErrorMessage(thumber);
return RedirectToAction("Index");
}
var item = new Member
{
Name = input.Name,
Mail = input.Mail,
SurName = input.SurName,
Phone = input.Phone,
Sira = Db.MyMembers.Max(m => (short?)m.Sira) ?? 0 + 1,
DepartmentType = (DepartmentTypeForUser)input.DepartmentTypeFor,
MemberType = (MemberTypeForUser)input.MemberTypeFor,
Image = "/" + folders[1] + orjName
};
item.SetPassword(input.Password);
Db.MyMembers.Add(item);
Db.SaveChanges();
ImageResizeModel model = new ImageResizeModel()
{
ImagePath = "/" + folders[1] + orjName,
ImageThumbPath = "/" + folders[1] + orjName,
SelectionSize = "[ 750, 750 ]",
};
return View("CropImage", model);
}
So How can I use multiple editorforModel for multiple times with different model field ? can I do anything in NewMemberModel class something like 2 methods and then call editorforModelMethod1 - editorforModelMethod2 ?

It's not entirely clear to me what you're asking (especially "controller won't allow", an actual error message would help us and could help you research the issue), but it looks like you could use a composite viewmodel:
public class NewMemberWithDashboardModel
{
public NewMemberModel NewMember { get; set; }
public MemberDashboardModel MemberDashboard { get; set; }
}
Then use it like this:
#Html.EditorFor(m => m.NewMember)
#Html.EditorFor(m => m.MemberDashboard)
And in your controller:
public ActionResult Add(NewMemberWithDashboardModel model, ...)

Related

X.PagedList, manual Paging and a pre-existing webapi for data only advance 1 record at a time

Setting up a PagedList in .netcore against a webapi that already exists.
The webapi allows me to call data using a 2 parameters, a FROM and a TAKE. I've implemented X.PagedList and it appears to work except for how it/I calculate the actual Page..
Using the code at X.PagedList, I implemented the Manual Paging. The issue is that when I click on a page number, my TAKE only takes 1 new record, as opposed to the NEXT 10.
On the first load, my api call looks like this
/api/v1/Institutions?from=0&take=10
The Page 2 call looks like
/api/v1/Institutions?from=1&take=10
Obviously the 1 should be 11 i think in this case
PagedResults Class
public class PagedResults<T> : List<T>
{
public List<T> Results { get; set; }
public string Search { get; set; }
public bool Empty { get; set; }
}
My Controller
public IActionResult PagedList(int? page)
{
ViewData["Title"] = Title;
ViewData["PageTitle"] = Title + " List";
var pageIndex = (page ?? 1) - 1;
var pageSize = 10;
//Perform API Call
var response = GetList(pageIndex, pageSize);
//Returns List<Institution>
var Institutions = response.Data;
//Returns 200, which is the total from the Headers
string total = response.Headers.Where(x => x.Name == "total").Select(x => x.Value).FirstOrDefault().ToString();
var PagedList = new StaticPagedList<Institution>(Institutions, pageIndex + 1, pageSize, Convert.ToInt32(total));
var model = new InstitutionViewModel
{
CurrentUser = CurrentUser //From BaseController
};
string view = string.Format("~/views/Portal/{0}/List.cshtml", Title);
ViewData["PagedList"] = PagedList;
return View(view, model);
}
GetList()
#region Helpers
public static IRestResponse<List<Models.Institution>> GetList(int from, int take)
{
//this create /api/v1/institutions?from=0&Take=10
string ActionPath = string.Format("Institutions");
var client = new RestClient(Connect.url);
var request = new RestRequest(ActionPath, Method.GET);
request.AddParameter("from", from, ParameterType.QueryString);
request.AddParameter("take", take, ParameterType.QueryString);
var result = client.Execute<List<Models.Institution>>(request);
return result;
}
#endregion
My View
#using X.PagedList.Mvc.Core;
#using X.PagedList;
#foreach (var item in ViewBag.PagedList)
{
<tr>
<td>#item.id</td>
<td>#item.name</td>
</tr>
}
#Html.PagedListPager((IPagedList)ViewBag.PagedList, page => Url.Action("PagedList", new { page }))
Here is a simple workaround like below:
1.View:
#{
ViewBag.Title = "Product Listing";
var pagedList = (IPagedList)ViewBag.PageList;
}
#using X.PagedList.Mvc.Core; #*import this so we get our HTML Helper*#
#using X.PagedList; #*import this so we can cast our list to IPagedList (only necessary because ViewBag is dynamic)*#
#using X.PagedList.Mvc.Common
#using X.PagedList.Mvc.Core.Fluent
<ul>
#foreach (var name in ViewBag.PageList)
{
<li>#name</li>
}
</ul>
#Html.PagedListPager(pagedList, page => Url.Action("Index", new { page }))
2.Controller:
[HttpGet]
public IActionResult Index(int page = 1)
{
ViewBag.PageList = GetPagedNames(page);
return View();
}
protected IPagedList<string> GetPagedNames(int? page)
{
// return a 404 if user browses to before the first page
if (page.HasValue && page < 1)
return null;
// retrieve list from database/wherever
var listUnpaged = new List<string> { "a", "b", "c", "d", "e", "aa", "bb", "cc", "dd", "ee", "aaa", "bbb", "ccc" ,"ddd","eee","fff","ggg","1","s","f","sd","dsfds"};
// page the list
const int pageSize = 10;
var listPaged = listUnpaged.ToPagedList(page ?? 1, pageSize);
// return a 404 if user browses to pages beyond last page. special case first page if no items exist
if (listPaged.PageNumber != 1 && page.HasValue && page > listPaged.PageCount)
return null;
return listPaged;
}

How to call action result return json on another action result on asp.net core 2.2?

Problem
How to call Action Result on another Action Result ?
I have two Action Result PostUserLogins and Action Result GetBranches
Can I call ActionResult getbranches inside ActionResult postlogin ?
[HttpPost(Contracts.ApiRoutes.Login.UserLogin)]
public IActionResult PostUserLogins([FromBody] Users user)
{
if (LoginStatus == 1)
{
// for Invalid Username Or password
dynamic request_status = new JObject();
request_status.Status = "failed";
request_status.Code = LoginStatus;
request_status.Message = errorMessage;
request_status.Branches = ????? How to call GetBranches Action;
// call action result to get GetBranches(Users user) as json;
JsonResults = "request_status" + JsonConvert.SerializeObject(request_status);
}
}
[HttpGet(Contracts.ApiRoutes.Login.GetBranches)]
public IActionResult GetBranches([FromRoute] string UserId)
{
List<Branches> branchesList = new List<Branches>();
for (int i = 0; i < dtBranches.Rows.Count; i++)
{
Branches branch = new Branches();
branch.BranchCode = Utilities.ObjectConverter.ConvertToInteger(dtBranches.Rows[i]["BranchCode"]);
branch.BranchName = Utilities.ObjectConverter.ConvertToString(dtBranches.Rows[i]["BranchAraName"]);
branchesList.Add(branch);
}
JsonResults = "request_status" + JsonConvert.SerializeObject(branchesList);
return Ok(JsonResults);
}
Regardless whether you could or not, you shouldn't.
The simplest way is to extract that logic to another method:
[HttpPost(Contracts.ApiRoutes.Login.UserLogin)]
public IActionResult PostUserLogins([FromBody] Users user)
{
if (LoginStatus == 1)
{
// for Invalid Username Or password
dynamic request_status = new JObject();
request_status.Status = "failed";
request_status.Code = LoginStatus;
request_status.Message = errorMessage;
request_status.Branches = GetBrancesImpl();
JsonResults = "request_status" + JsonConvert.SerializeObject(request_status);
}
}
[HttpGet(Contracts.ApiRoutes.Login.GetBranches)]
public IActionResult GetBranches([FromRoute] string UserId)
{
JsonResults = "request_status" + JsonConvert.SerializeObject(GetBrancesImpl());
return Ok(JsonResults);
}
private IEnumerable<Branches> GetBrancesImpl()
{
from branch in dtBranches.Rows
select new new Branches
{
BranchCode = Utilities.ObjectConverter.ConvertToInteger(dtBranches.Rows[i]["BranchCode"]),
BranchName = Utilities.ObjectConverter.ConvertToString(dtBranches.Rows[i]["BranchAraName"]),
};
}
Best would be to move this logic to a service class that holds the logic and can easily be tested.
If they are in the same controller,you could call it directly in PostUserLogins like:
public IActionResult PostUserLogins([FromBody] Users user)
{
//other logic
var result = GetBranches("myUserID") as OkObjectResult;
var json = result.Value.ToString().Substring(14);//remove the first "request_status" in the string to make it a valid json be deserialized later
request_status.Branches = JsonConvert.DeserializeObject<List<Branch>>(json);//get the Branch list
JsonResults = "request_status" + JsonConvert.SerializeObject(request_status);
}

Populating Nested List<> in MVC4 C#

I've got a problem populating nested List<>
The object graph looks like this:
Route ⇒ Section ⇒ Co-ordinates
Whenever I try to populate Сoordinates list it just overwrites previous record and at the end gives me only the last Coordinate record. But I want all the Co-ordinates.
Here is my controller code:
List<RequestRouteDataClass> result = new List<RequestRouteDataClass> {
new RequestRouteDataClass() {
RouteRequestId = objRouteManagement.RouteRequestId,
RouteName = objRouteManagement.RouteName,
RouteDescription = objRouteManagement.RouteDescription,
RouteSections = new List<RouteSections> {
new RouteSections() {
Route_Sections_Id = objSections.Route_Sections_Id,
Section_Speed = objSections.Section_Speed,
Section_Description = objSections.Section_Description,
RouteCordinatesSections = new List<SectionCoordinatesRelationData> {
new SectionCoordinatesRelationData() {
SectionCoordinate_Relat_Id = objSectionsCordinates.SectionCoordinate_Relat_Id,
CoordinateLat = objSectionsCordinates.CoordinateLat,
CoordinateLag = objSectionsCordinates.CoordinateLag
}
}
}
}
}
If you want to use Nested List.
Your Model Contains =>
public class MainModelToUse
{
public MainModelToUse()
{
FirstListObject = new List<FirstListClass>();
}
public List<FirstListClass> FirstListObject { get; set; }
}
public class FirstListClass
{
public FirstListClass()
{
SecondListObject = new List<SecondListClass>();
}
public List<SecondListClass> SecondListObject { get; set; }
}
public class SecondListClass
{
public SecondListClass()
{
ThirdListObject = new List<ThirdListClass>();
}
public List<ThirdListClass> ThirdListObject { get; set; }
}
public class ThirdListClass
{
}
Your Code to Nested List =>
FirstListClass vmFirstClassMenu = new FirstListClass();
vmFirstClassMenu.SecondListClass = new List<SecondListClass>();
FirstListClass vmFirstClassCategory = new FirstListClass();
var dataObject1 = //Get Data By Query In Object;
foreach (Model objModel in dataObject1)
{
vmFirstClassCategory = new FirstListClass
{
//Your Items
};
var DataObject2 = //Get Data By Query In Object;
vmFirstClassCategory.SecondListClass = new List<SecondListClass>();
foreach (SecondListClass menuItem in DataObject2)
{
SecondListClass vmFirstClassMenuItem = new SecondListClass
{
//Your Items
};
var DataObject3 = //Get Data By Query In Object;
vmFirstClassMenuItem.ThirdListClass = new List<ThirdListClass>();
foreach (ThirdListClass price in DataObject3)
{
ThirdListClass vmThirdClassobj = new ThirdListClass
{
//Your Items
};
vmFirstClassMenuItem.ThirdListClass.Add(vmThirdClassobj);
}
vmFirstClassCategory.SecondListClass.Add(vmFirstClassMenuItem);
}
}
Hope this is what you are looking for.
First off: spacing helps with readability (edit: but I see you fixed that in your question already):
List<RequestRouteDataClass> result = new List<RequestRouteDataClass>
{
new RequestRouteDataClass()
{
RouteRequestId = objRouteManagement.RouteRequestId,
RouteName = objRouteManagement.RouteName,
RouteDescription = objRouteManagement.RouteDescription,
RouteSections = new List<RouteSections>
{
new RouteSections()
{
Route_Sections_Id = objSections.Route_Sections_Id,
Section_Speed = objSections.Section_Speed,
Section_Description = objSections.Section_Description,
RouteCordinatesSections = new List<SectionCoordinatesRelationData>
{
new SectionCoordinatesRelationData()
{
SectionCoordinate_Relat_Id = objSectionsCordinates.SectionCoordinate_Relat_Id,
CoordinateLat = objSectionsCordinates.CoordinateLat,
CoordinateLag =objSectionsCordinates.CoordinateLag
}
}
}
}
}
};
Next: what you are doing with the above is initiating your lists with a single element in each list. If you want more elements, you have to add them. I recommend using a foreach and the Add() functionality to fill your lists.
From your example it is not clear how your source data is stored, but if you have multiples of something I would expect those too to be in a list or an array of some kind.

send parameter to windows azure mobile server script in c# for Windows 8 Store app

I modified the "Read" operation on my Windows Azure Mobile Services Preview table (named "Item") as follows:
Javascript:
function read(query, user, request)
{
var howRead;
if(howRead == "unique")
{
var sqlUnique = "SELECT DISTINCT ? FROM Item WHERE qProjectCode = ?";
mssql.query(sqlUnique)
request.execute();
}
else if (howRead == "column")
{
var sqlColumn = "SELECT ? FROM Item WHERE qProjectCode = ?";
mssql.query(sqlColumn)
request.execute();
}
else if (howRead == "all")
{
var sqlAll = "SELECT * FROM Item WHERE qProjectCode = ?";
mssql.query(sqlAll)
request.execute();
}
}
This simply species when I want a unique list of a single column's values returned, all items in a single column, or all columns, respectively, all while limiting the read to those records with a given project code.
Right now, this works in C#, but scans the entire table (with other project codes) and always returns all columns. This is inherently inefficient.
c#
var client = new MobileServiceClient("[https path", "[key]");
var table = client.GetTable<Item>();
var query1 = table.Where(w => w.QProjectCode == qgv.projCode && w.QRecord == (int)lbRecord.Items[uStartRecordIndex]);
var query1Enum = await query1.ToEnumerableAsync();
foreach (var i in query1Enum)
{
// process data
}
How do I alter the c# code to deal with the Javascript code? Feel free to critique the overall approach, since I am not a great programmer and can always use advice!
Thanks
A few things:
In your server code, the mssql calls are not doing anything (useful). If you want to get their results, you need to pass a callback (the call is asynchronous) to it.
Most of your scenarios can be accomplished at the client side. The only for which you'll need server code is the one with the DISTINCT modifier.
For that scenario, you'll need to pass a custom parameter to the server script. You can use the WithParameters method in the MobileServiceTableQuery<T> object to define parameters to pass to the service.
Assuming this data class:
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Other { get; set; }
public string ProjectCode { get; set; }
}
The code below can be used to accomplish the scenarios 2 and 3 at the client side only (no script needed at the server side). The other one will need some script, which I'll cover later.
Task<IEnumerable<string>> ReadingByColumn(IMobileServiceTable<Item> table, string projectCode)
{
return table
.Where(i => i.ProjectCode == projectCode)
.Select(i => i.Name)
.ToEnumerableAsync();
}
Task<IEnumerable<Item>> ReadingAll(IMobileServiceTable<Item> table, string projectCode)
{
return table.Where(i => i.ProjectCode == projectCode).ToEnumerableAsync();
}
Task<IEnumerable<string>> ReadingByColumnUnique(IMobileServiceTable<Item> table, string projectCode)
{
var dict = new Dictionary<string, string>
{
{ "howRead", "unique" },
{ "projectCode", projectCode },
{ "column", "Name" },
};
return table
.Select(i => i.Name)
.WithParameters(dict)
.ToEnumerableAsync();
}
Now, to support the last method (which takes the parameters, we'll need to do this on the server script:
function read(query, user, request)
{
var howRead = request.parameters.howRead;
if (howRead) {
if (howRead === 'unique') {
var column = request.parameters.column; // WARNING: CHECK FOR SQL INJECTION HERE!!! DO NOT USE THIS IN PRODUCTION!!!
var sqlUnique = 'SELECT DISTINCT ' + column + ' FROM Item WHERE ProjectCode = ?';
mssql.query(sqlUnique, [request.parameters.projectCode], {
success: function(distinctColumns) {
var results = distinctColumns.map(function(item) {
var result = [];
result[column] = item; // mapping to the object shape
return result;
});
request.respond(statusCodes.OK, results);
}
});
} else {
request.respond(statusCodes.BAD_REQUEST, {error: 'Script does not support option ' + howRead});
}
} else {
// no server-side action needed
request.execute();
}
}

How to build object hierarchy from SQL query? (for WPF TreeView)

thanks for taking the time out to read this post.
I'm having trouble trying to build a hierarchial object when getting data from my SQL database.
Please note that I am a little bit of a newbie programmer.
How do you build a hierarchial object that has unknown levels? When I say unknown levels I mean, each node may have varying numbers of child nodes, which in turn may have varying numbers of its own child nodes, so on and so on.
The idea is that I need to create a hierarchial object using my SQL data to bind to WPF TreeView control.
Below I have included the code I have so far.
The first bit of code is my Class made up of Properties. Note that the "Products" class has an ObservableCollection referencing itself. I think this is how you construct the nested nodes. i.e. a list inside a list.
The second piece of code is my Get Method to download the data from the SQL database. Here is where I need to some how sort the downloaded data into a hierarchy.
Products Class (properties)
public class Products : INotifyPropertyChanged, IDataErrorInfo
{
private Int64 m_ID;
private SqlHierarchyId m_Hierarchy;
private string m_Name;
private ObservableCollection<Products> m_ChildProducts;
// Default Constructor
public Products()
{
ChildProducts = new ObservableCollection<Products>();
}
//Properties
public Int64 ID
{
get
{
return m_ID;
}
set
{
m_ID = value;
OnPropertyChanged(new PropertyChangedEventArgs("ID"));
}
}
public SqlHierarchyId Hierarchy
{
get
{
return m_Hierarchy;
}
set
{
m_Hierarchy = value;
OnPropertyChanged(new PropertyChangedEventArgs("Hierarchy"));
}
}
public String Name
{
get
{
return m_Name;
}
set
{
m_Name = value;
OnPropertyChanged(new PropertyChangedEventArgs("Name"));
}
}
public Int16 Level
{
get
{
return m_Level;
}
set
{
m_Level = value;
OnPropertyChanged(new PropertyChangedEventArgs("Level"));
}
}
public Int64 ParentID
{
get
{
return m_ParentID;
}
set
{
m_ParentID = value;
OnPropertyChanged(new PropertyChangedEventArgs("ParentID"));
}
}
public ObservableCollection<Products> ChildProducts
{
get
{
return m_ChildProducts;
}
set
{
m_ChildProducts = value;
OnPropertyChanged(new PropertyChangedEventArgs("ChildProducts"));
}
}
//INotifyPropertyChanged Event
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
}
Method which gets data from SQL DB:
public static ObservableCollection<Products> GetProductsHierarchy()
{
ObservableCollection<Products> productsHierarchy = new ObservableCollection<Products>();
SqlConnection connection = new SqlConnection(DBConnection.GetConnection().ConnectionString);
string selectStatement = "SELECT ID, Hierarchy, Name, Hierarchy.GetLevel() AS Level, Hierarchy.GetAncestor(1) AS ParentHierarchy, " +
"(SELECT ID " +
"FROM SpecProducts " +
"WHERE (Hierarchy = SpecProducts_1.Hierarchy.GetAncestor(1))) AS ParentID " +
"FROM SpecProducts AS SpecProducts_1 " +
"WHERE (EnableDisable IS NULL) " +
"ORDER BY Hierarchy";
SqlCommand selectCommand = new SqlCommand(selectStatement, connection);
try
{
connection.Open();
SqlDataReader reader = selectCommand.ExecuteReader();
while (reader.Read())
{
Products product = new Products();
product.ID = (Int64)reader["ID"];
product.Name = reader["Name"].ToString();
product.Hierarchy = (SqlHierarchyId)reader["Hierarchy"];
product.Level = (Int16)reader["Level"];
if (reader["ParentID"] != DBNull.Value)
{
product.ParentID = (Int64)reader["ParentID"];
}
else
{
product.ParentID = 0;
}
productsHierarchy.Add(product);
// *** HOW TO BUILD HIERARCHY OBJECT WITH UNKNOWN & VARYING LEVELS?
// *** ADD PRODUCT TO CHILDPRODUCT
}
return productsHierarchy;
}
catch (SqlException ex)
{
throw ex;
}
finally
{
connection.Close();
}
}
Below I have attached an image showing the structure of my SQL Query Data.
Please note that the hierarchy level may go deeper when more products are added in the future. The Hierarchy object I need to create should be flexible enough to expand no matter what the number of node levels are.
Thank you very much for your time, all help is greatly appreciated.
********* EDIT 26/04/2012 14:37 *******************
Please find below a link to download my project code (this only contains treeview code).
Can someone please take a look at it to see why I cannot create nodes beyond 2 levels?
The code was given to me by user HB MAAM. Thank you "HB MAAM" for your help so far!
Click this link to download code
I will create an example for you,
1- first i will create a class that holds the data that comes from the DB
public class SqlDataDto
{
public int? Id { get; set; }
public int? ParentId { get; set; }
public String Name { get; set; }
public String OtherDataRelatedToTheNode { get; set; }
}
2- that data will be converted to hierarchal data and we will use this class to hold it:
public class LocalData : INotifyPropertyChanged
{
private int? _id;
public int? Id
{
get { return _id; }
set { _id = value; OnPropertyChanged("Id"); }
}
private int? _parentId;
public int? ParentId
{
get { return _parentId; }
set { _parentId = value; OnPropertyChanged("ParentId"); }
}
private string _name;
public String Name
{
get { return _name; }
set { _name = value; OnPropertyChanged("Name"); }
}
private string _otherDataRelatedToTheNode;
public String OtherDataRelatedToTheNode
{
get { return _otherDataRelatedToTheNode; }
set { _otherDataRelatedToTheNode = value; OnPropertyChanged("OtherDataRelatedToTheNode"); }
}
private LocalData _parent;
public LocalData Parent
{
get { return _parent; }
set { _parent = value; OnPropertyChanged("Parent"); }
}
private ObservableCollection<LocalData> _children;
public ObservableCollection<LocalData> Children
{
get { return _children; }
set { _children = value; OnPropertyChanged("Children"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,new PropertyChangedEventArgs(propertyName));
}
}
}
3- finally we need to change the sql data to hierarchical one:
public List<LocalData> GetHerachy(List<SqlDataDto> sqlData)
{
var sqlParents = sqlData.Where(q => q.ParentId == null).ToList();
var parents = sqlParents.Select(q => new LocalData {Id = q.Id, Name = q.Name}).ToList();
foreach (var parent in parents)
{
var childs = sqlData.Where(q => q.ParentId == parent.Id).Select(q => new LocalData { Id = q.Id, Name = q.Name , Parent = parent});
parent.Children = new ObservableCollection<LocalData>(childs);
}
return parents;
}
4- then you can create a dummy data and convert it and show it in the tree:
var sqlData = new List<SqlDataDto>
{
new SqlDataDto {Id = 1, ParentId = null, Name = "F1"}
, new SqlDataDto {Id = 2, ParentId = null, Name = "F2"}
, new SqlDataDto {Id = 3, ParentId = 1, Name = "S1"}
, new SqlDataDto {Id = 4, ParentId = 2, Name = "S21"}
, new SqlDataDto {Id = 5, ParentId = 2, Name = "S22"}
};
treeView.ItemsSource = GetHerachy(sqlData);
5- the tree should be like:
<TreeView Name="treeView">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
You need to use recursion to fill the Child-List of every object. This is necessary for the WPF HierarchicalDataTemplate to work. Otherwise you only get the first level.
There is an alternative using the Linq method ForEach() and passing an Action Argument. The following solution is very straight forward and easy to understand:
public List<Product> Products { get; set; }
public MainViewModel()
{
Products = new List<Product>();
Products.Add(new Product() { Id = 1, Name = "Main Product 1", ParentId = 0 });
Products.Add(new Product() { Id = 3, Name = "Sub Product 1", ParentId = 1 });
Products.Add(new Product() { Id = 4, Name = "Sub Product 2", ParentId = 1 });
Products.Add(new Product() { Id = 5, Name = "Sub Product 3", ParentId = 1 });
Products.Add(new Product() { Id = 6, Name = "Sub Product 3.1", ParentId = 5 });
this.ProcessRootNodes();
}
private void ProcessRootNodes()
{
var rootNodes = Products.Where(x => x.ParentId == 0).ToList();
for (int i = 0; i < rootNodes.Count; i++)
{
rootNodes[i].Children = this.AddChildren(rootNodes[i]);
}
}
private List<Product> AddChildren(Product entry)
{
var children = Products.Where(x => x.ParentId == entry.Id).ToList();
for(int i=0;i<children.Count;i++)
{
children[i].Children = this.AddChildren(children[i]);
}
return children;
}
// *** HOW TO BUILD HIERARCHY OBJECT WITH UNKNOWN & VARYING LEVELS?
Instead of
ObservableCollection<Products> productsHierarchy = new ObservableCollection<Products>();
use Dictionary<Int64, Products> IdToProduct = new ...
As you loop your products; do a IdToProduct[product.ID] = product;
Then, loop the completed IdToProduct collection and do;
if(product.ParentID != 0)
{
IdToProduct[product.ParentID].ChildProducts.Add(product);
}
Now, your Product --> ChildProducts relation is mapped out.
Optionally, add properties to the Products class:
public bool IsCategory { get { return (ChildProducts.Count >= 1); } } // e.g. Oven
public bool IsProduct { get { return !(IsCategory); } } // e.g. Electric (Oven)
Now, you have most of the model for your view defined.
This article is the de facto starting point for using the WPF TreeView.
Hint: a starting point for your HierarchicalDataTemplate
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Products}"
ItemsSource="{Binding ChildProducts}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
You should create a MainViewModel class which has:
public Products RootProduct { get; set; } (notify property changed property)
after you do your SQL parsing and what not; do:
RootProduct = IdToProduct.FirstOrDefault(product => (product.Level == 0));
<TreeView ItemsSource="{Binding RootProduct.ChildProducts}">