PDA

View Full Version : سوال: افزودن child node در یک treeview و ذخیره آن



khokhan
شنبه 30 دی 1391, 21:24 عصر
با سلام به همه دوستان

من با استفاده از این تکه کد نود های اصلی یک treeviewرو داخل یک جدول در دیتابیس می ریزم

اما زمانی که treeview دارای نود های فرزند باشد با این شیوه چگونه می توان " هم نودهای اصلی وهم نود های

فرزند " و به اصطلاح child nod ها رو باهم در یک ستون جداگانه ذخیره کرد


خواهشا اگه راه حلی دارین راهنمایی کنین

کد :


private void button2_Click(object sender, EventArgs e)
{
string constr = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Application.StartupPath + "\\dbs.mdb";
OleDbConnection con = new OleDbConnection(constr);
con.Open();
foreach (TreeNode tn in this.treeView1.Nodes)
{
string cmdstr1 = @"insert into Node1(NodeName) values(@nodeName)";
using (OleDbCommand cmd = new OleDbCommand(cmdstr1, con))
{
OleDbParameter sp = new OleDbParameter("@nodeName", tn.Text);
cmd.Parameters.Add(sp);
cmd.ExecuteNonQuery();
}
}
con.Close();
}

RED-C0DE
شنبه 30 دی 1391, 22:31 عصر
می خوای در واقع کل نودهای درخت رو تو یک جدول ذخیره کنی؟
ساختار جدولت چجوریه؟
آیا درختت Nسطحیه؟


اگه بخوای کل ساختار درخت رو (نودهای اصلی با هرچی نود فرزند هست) در بانک ذخیره و بعدا بازیابی کنی، ی راهکار اینه همچین جدولی داشته باشی:


Id
NodeText
ParentNodeId
...

روال ذخیره و بازیابی هم بصورت بازگشتی انجام می شه

khokhan
شنبه 30 دی 1391, 22:38 عصر
می خوای در واقع کل نودهای درخت رو تو یک جدول ذخیره کنی؟
ساختار جدولت چجوریه؟
آیا درختت Nسطحیه؟


اگه بخوای کل ساختار درخت رو (نودهای اصلی با هرچی نود فرزند هست) در بانک ذخیره و بعدا بازیابی کنی، ی راهکار اینه همچین جدولی داشته باشی:


Id
NodeText
ParentNodeId
...

روال ذخیره و بازیابی هم بصورت بازگشتی انجام می شه

شما فرض کنین این تری ساختار یه مدرسه رو نشون می ده
اینطوری

مدرسه
پایه ها
کلاسها

ممنون از توجه تون

khokhan
شنبه 30 دی 1391, 22:41 عصر
حالا اون کد بالایی رو می شه یه جوری تغییر داد که این کار رو بکنه ؟

فرید نجفلو
شنبه 30 دی 1391, 23:05 عصر
شما حتما باید اون دو فیلدی که دوستمون گفتن رو (آی دی خود نود و آی دی پدرش) رو تو جدول داشته باشید
و نظر شون در مورد بازگشتی بودن هم کاملا صحیحه

khokhan
یک شنبه 01 بهمن 1391, 14:02 عصر
با سلام

آقا ما جداول رو ساختیم تموم شده رفته پی کارش

تری ویو رو هم بایند کردم عمل هم می کنه

مشکل ما اینه که وقته برروی نود ها و چایلد نود ها تغییراتی انجام دادیم حالا می خواهیم تغییرات ذخیره بشه یا دیتابیس رو آپدید کنیم

من با اون تکه کد بال که نوشتم نود های اصلی ذخیره می شن اما چایلدها نه تکلیف چیست ؟

RED-C0DE
یک شنبه 01 بهمن 1391, 16:16 عصر
یعنی مشکل اینه ک نمی تونین علاوه بر نودهای اصلی ، بچه ها رو هم روشون حرکت کنید و عملیات مورد نظر رو انجام بدید؟

اگه اره ک همون می شه ک گفتم باید بصورت بازگشتی رو نودها حرکت کنی و بهشون برسی:


راه اول :
با کمک این متود:

public void appendNodes(List<TreeNode> pNodesList, TreeNode pNode)
{
pNodesList.Add(pNode);

pNode.NodesAsEnumerable().ToList().ForEach(n=> appendNodes(pNodesList,n));
//foreach (TreeNode itsChild in pNode.Nodes)
//{
// appendNodes(pNodesList, itsChild);
//}
}


نحوه استفاده :

List<TreeNode> listAllNodes = new List<TreeNode>();
foreach(TreeNode n in treeView.Nodes)
{
appendNodes(listAllNodes, n);
}

این برای حالتیه ک یک نود بعنوان ریشه ی اصلی در درخت (ک معمولا صوری هم هست) تعریف نشده باشه،‌ و مجبوریم روی تمام نودهای اصلی (چون در سطح 0 درخت هستن) حرکت کنیم و در تابع appendNodes() ب انتهای لیست listAllNodes اضافه کنیم.
اگه ک یک نود در درخت بعنوان ریشه تعریف بشه در هنگام استفاده ب این صورت می شه استفاده کرد:
List<TreeNode> listAllNodes1 = new List<TreeNode>();
appendNodes(listAllNodes1, treeView1.Nodes[0]);

توی متود appenNodes() من از یک ExtensionMethod استفاده کردم ب اسم NodesAsEnumerable() ک بعلاوه ی چند extension method پرکاربرد دیگه (مخصوصا تابع AsFlatten) براتون در انتهای این پست می ذارمش.
قبل اون راه دوم رو می ذارم ک با از این توابع کمک گرفته شده (مزیت این توابع اینه واسه خیلی کارای دیگه می تونین ازشون استفاده کنین) :

var allNodes2 = treeView1.GetAllNodes();

RED-C0DE
یک شنبه 01 بهمن 1391, 16:19 عصر
اینم چندتابع سودمند :

public static class RcExtensions
{
public static List<TreeNode> GetAllNodes(this TreeView pTreeView)
{
var retVal = new List<TreeNode>();

var itsRoots = pTreeView.Nodes.Cast<TreeNode>();
foreach (var rootNode in itsRoots)
{
retVal.AddRange(rootNode.AsFlatten(x => x.Nodes.OfType<TreeNode>()));
}

return retVal;
}

public static IEnumerable<TreeNode> NodesAsEnumerable(this TreeView pTreeView)
{
return pTreeView.Nodes.Cast<TreeNode>();
}
public static IEnumerable<TreeNode> NodesAsEnumerable(this TreeNode pTreeNode)
{
return pTreeNode.Nodes.Cast<TreeNode>();
}

public static IEnumerable<T> AsFlatten<T>(this T pRoot, Func<T, IEnumerable<T>> pFuncGetItsChilds)
{
yield return pRoot;

var itsDescendants = pFuncGetItsChilds(pRoot);
if (itsDescendants == null)
yield break;

foreach (var itsInnerChild in itsDescendants.SelectMany(itsChild => AsFlatten(itsChild, pFuncGetItsChilds)))
{
yield return itsInnerChild;
}
}
}


مهمترینشون تابع AsFlatten هست. می تونین در بسیاری از ساختارهای درختی ازش استفاده کنین. برای اینکه ساختار درختی مورد نظرتون رو ب یک ساختار خطی تبدیل کنید

khokhan
یک شنبه 01 بهمن 1391, 16:38 عصر
با سلام مجدد حضور تمام دوستان

می بخشید از اینکه من نتونستم منظور اصلی مو از این مسئله برسونم

بگذارین اینطور بگم من در این برنامه اطلاعات موجود در یک تیبل در دیتابیس رو توی تری ویو بایند کردم ( هم نود های چایلد رو وهم نود های اصلی رو )

حالا با استفاده از یک باتن وچند تا تکس باکس به تعداد نودها اضافه می کنم ( چه نود اصلی وچه نود فرزند )

حالا اگه قرارباشه تغییرات رو به اون تیبل انتقال بدیم یا به اصطلاح دیتابیس رو آپدیت کنیم بطوری که هم نود های اصلی ذخیره بشن وهم نود های فرزند

در رویداد کلیک باتن 2 که نیمچه کدش رو بالا نوشتم چه تغییری باید بدیم

RED-C0DE
یک شنبه 01 بهمن 1391, 16:45 عصر
اگه هر نود شما در نقش یک business entity عمل می کنه کارتون راحته. پس از اینکه نودی ب نودها اضافه/حذف شد (رویدادهای مورد نظرشون رو هندل کنید) در رویداد مربوطه ، موجودیت رو از روی اون نود در بیارید و همون نود رو در بانک بروز/اضافه کنید.

یا

هنگام ساخت درخت ، در خاصیت Tag مربوط ب هر نود ،‌ Id معادل در جدول رو روی اون نود بزارین. نود جدید اگه اضافه بشه ب یک نود پدر،‌شما id پدر رو دارین و حالا توی رویدادهای مربوطه ، زیر نود پدر، این نود جدید رو اضافه کنید.


شما ساختار جدولتون رو نذاشتین ک کسی بتونه کمک کنه!
اینکه کدش چجوری باشه رو دیگه امیدوارم اگه این کارتو راه ننداخت کسی بیاد بنویسه چون من دیگه باید برم

khokhan
یک شنبه 01 بهمن 1391, 16:57 عصر
ساختار جدول من اینطوریه :

جدول اصلی :
1 - آی دی نود اصلی

2 - نام نود اصلی


جدول فرعی

1 آی دی نود اصلی

2 - آی دی نود فرزند

3 - نام نود فرزند

khokhan
یک شنبه 01 بهمن 1391, 20:38 عصر
این هم از ساختار جدول ما حالا یکی از دوستان راجع به ذخیره تغییرات نودها ی اصلی و چایلدها یtree در دیتابیس نظر بده

Mahmoud.Afrad
دوشنبه 02 بهمن 1391, 04:03 صبح
برای ذخیره نودها یک جدول کافیه و حتی کار رو آسون تر هم میکنه ، چون هر نود میانی همزمان هم parent و هم child هست. در صورتی که اگر دو جدول داشته باشید برای اینگونه گره ها مجبورید اطلاعات اون گره رو در هر دو جدول ذخیره کنید.
در ثانی شما با این دو جدول چه طور سطح اول رو تشخیص میدید؟! اگر قرار بر این باشه که سطح اول رو با Parentid برابر 0 در نظر بگیرید دیگه نیازی به جدول اول نیست. اگر هم جدول اول فقط برای سطح اول باشه سطوح بعدی رو مجبورید در جدول دوم ذخیره کنید که باز هم میشه سطح اول رو به جدول دوم اضافه کرد.
پس همونطور که دوستان هم گفتند با یک جدول باید کار کنید.

جدولی به صورت زیر :
id -> int
text -> nvarchar(max)
parentID -> int
برای نودهای سطح اول ParentID را null قرار بدید.

جهت لود گره ها ، اطلاعات جدول را براساس ParentID به صورت صعودی مرتب کرده و براساس ParentID نودها رو اضافه کنید:

private void LoadNodes()
{
treeView1.Nodes.Clear();

cmd = new SqlCommand("SELECT ID,Text,ParentID from tblNodes ORDER BY ParentID", con);
con.Open();
SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
if (Convert.ToString(dr["ParentID"]) == string.Empty)
{
treeView1.Nodes.Add(new TreeNode() { Name = Convert.ToString(dr["ID"]), Text = Convert.ToString(dr["text"]) });
}
else
{
TreeNode[] tn = treeView1.Nodes.Find(Convert.ToString(dr["ParentID"]), true);
if (tn[0] != null)
{
tn[0].Nodes.Add(new TreeNode() { Name = Convert.ToString(dr["ID"]), Text = Convert.ToString(dr["text"]) });
}
}
}

dr.Dispose();
con.Close();
}

id را در خصوصیت name مربوط به treenode ذخیره کنید تا براساس همین مشخصه بتونید گره رو find کنید.

برای اضافه کردن گره جدید ، در گره پدر(اینجا گره select شده) مشخصه name همان Parentid خواهد بود:

private void AddNode(TreeNode parentNode , string newChildText)
{
int parentId = int.Parse(parentNode.Name);

cmd = new SqlCommand("Insert into tblNodes(text,parentid) values(@text,@parentid)", con);
cmd.Parameters.AddWithValue("@text", newChildText);
cmd.Parameters.AddWithValue("@parentid", parentId);
con.Open();
cmd.ExecuteNonQuery();

cmd.CommandText = "SELECT ID from tblNodes WHERE text=@text AND parentID=@Parentid";
SqlDataReader dr = cmd.ExecuteReader();
dr.Read();
TreeNode tnNewChild = new TreeNode() { Name = Convert.ToString(dr["ID"]), Text = newChildText };
treeView1.Nodes.Find(parentId.ToString(), true)[0].Nodes.Add(tnNewChild);

dr.Dispose();
con.Close();
}

به اینصورت استفاده کنید:

TreeNode tnSelected = treeView1.SelectedNode;
if (tnSelected != null)
{
AddNode(tnSelected, textBox1.Text);
}

khokhan
دوشنبه 02 بهمن 1391, 20:40 عصر
با سلام مجدد به همه دوستان واستاد محترم

ازتوجه و راهنمایی همه دوستان بسیار سپاسگذارم

1 . اینکه بهتره برای اطلاعات فقط یک جدول داشته باشیم قبول

2 . اینکه چگونه اطلاعات رو از جدول بخونیم و توی یک tree نشونشون بدیم این هم قبول

اما اگه توی این tree گره های پدر یا گره های فرزند جدیدی افزودیم با همین روال ذخیره و بازیابی بصورت بازگشتی

چگونه یکجا در جدول ذخیره کنیم یعنی ابتدا همه تغییرات را در tree اعمال کنیم و بعد عمل ذخیره صورت گیرد


یه چیزی شبیه این :


private void Apply_Click(object sender, EventArgs e)
{
SqlConnection sqlconn = new SqlConnection(cs2);
sqlconn.Open();
SqlCommand sqlcmd = new SqlCommand("Delete from t1", sqlconn);
sqlcmd.ExecuteNonQuery();
sqlcmd.CommandText = "Delete from t2";
sqlcmd.ExecuteNonQuery();

int i1, i2, i3;
i1 = 0;
while (i1 < treeView1.Nodes.Count)
{
sqlcmd.CommandText = "insert into t1(lvl1) values('" + treeView1.Nodes[i1].Text + "')";
sqlcmd.ExecuteNonQuery();

i2 = 0;
while (i2 < treeView1.Nodes[i1].Nodes.Count)
{
sqlcmd.CommandText = "insert into t2(lvl2) values('" + treeView1.Nodes[i1].Text + '*' + treeView1.Nodes[i1].Nodes[i2].Text + "')";
sqlcmd.ExecuteNonQuery();
sqlcmd.CommandText = "update t1 set lvl2 = IsNull(lvl2,'') + '" + treeView1.Nodes[i1].Nodes[i2].Text + "'+'*' where lvl1 ='" + treeView1.Nodes[i1].Text + "' ";
sqlcmd.ExecuteNonQuery();

i3 = 0;
while (i3 < treeView1.Nodes[i1].Nodes[i2].Nodes.Count)
{
sqlcmd.CommandText = "update t2 set lvl3 = IsNull(lvl3,'') + '" + treeView1.Nodes[i1].Nodes[i2].Nodes[i3].Text + "'+'*' where lvl2 ='" + treeView1.Nodes[i1].Text + '|' + treeView1.Nodes[i1].Nodes[i2].Text + "' ";
sqlcmd.ExecuteNonQuery();
i3++;
}
i2++;
}
i1++;
}

sqlconn.Close();
}
}

khokhan
دوشنبه 02 بهمن 1391, 21:04 عصر
توضیح اینکه ما در بایند کردن وذخیره گره پدر مشکلی نداریم مشکل زمانی است که بخواهیم آخرین تغییرات در کل tree اعم از گره های پدر و گره های فرزند را یکجا

وارد جدول نماییم

به این صورت که پس از انجام تغییرات در tree حالا یا افزودن گره یا حذف گره

1 . بیائیم محتوای جدول رو پاک کنیم

2 . آخرین وضعیت tree رو در جدول ذخیره کنیم

RED-C0DE
دوشنبه 02 بهمن 1391, 23:01 عصر
اوکی
شما می خواین وضعیت درخت رو در جدول , بروز نگه دارین بصورت لحظه ای (یعنی هر وقت ک نودی اضافه/حذف شد جدول هم بروز بشه).
اگه ک یک TreeView سفارشی داشته باشین برای خودتون بهتره. و توی اون, عملیات Add/Remove نودها رو مدیریت کنید (یک Wrapper برای اینکار بنویسین (اگه تو نوشتنش مشکلی داشتین بگین) تا بتونین event های مربوطه رو (NodeAdded , NodeRemoved) هم بعد از افزودن/حذف نود fire کنید تا در ادامه کار ازشون استفاده کنید (چون TreeView خودش event ای برای فهمیدن افزودن/حذف نودها نداره!))

این فرض هم می کنم ک موقع ساخت درخت , شما Id هر نود رو در TreeNode.Tag نگه می دارین ("چرا" شو اگه نمی دونین بگین تا بگم). اگه جواب بله باشه کار راحتتر پیش می ره.

الان برنامه اجراس , درخت لود شده و هر نود Id خوشو در Tag داره.

هنگام افزودن نود جدید:
فرض کنیم نود جدید A , ب یک نود دیگه مثل B اضافه می شه.
در رویداد NodeAdded (ک قرار شد خودتون بنویسینش) , شما متوجه می شین ک نود A ب نود B با Id (مثلا) 10 اضافه شد.
حالا فقط کافیه یک رکورد ب جدول اضافه کنید (با اطلاعات مربوط ب نود A و اینکه ParentId این نود برابر با 10 یعنی نود B هست).

هنگام حذف یک نود:
فرض کنیم نود A از درخت حذف شد و قراره در بانک این عمل رو بروز کنید. (ممکنه این نود , برگ باشه یا فرزند داشته باشه)
در رویداد NodeRemoved (ک قرار شد خودتون بنویسینش) , شما متوجه می شین ک نود A از درخت حذف شده (ک مثلا Id=1432 رو داره).
حالا (بسته ب سیاست برنامه) می تونین این Id + تمام اونایی ک فرزند این نود (Id) هستن رو بصورت درختی (با یک StoredProcedure) حذف کنید.

khokhan
دوشنبه 02 بهمن 1391, 23:48 عصر
با سلام وتشکر

منظور من اینه که ما همه تغییرات رو انجام بدیم ووقتی ترکیب وترتیب گره ها اون طوری که مطابق با خواسته ما شد

با کلیک بر روی یک دکمه ابتدا محتویات قبلی جدول داخل دیتابیس پاک و محتویات تغییر یافته در tree به جای اطلاعات قبلی مون ذخیره بشه

khokhan
دوشنبه 02 بهمن 1391, 23:54 عصر
منظور از Wrapper یعنی یه چیزی شبیه این باشه ؟



public event EventHandler NodeAdded;

public void AddNode(TreeViewNode node)
{
Nodes.Add(node);
if (NodeAdded != null)
{
NodeAdded(this, EventArgs.Empty);
}
}

RED-C0DE
سه شنبه 03 بهمن 1391, 00:18 صبح
اگه تغییرات رو بخواین در همون لحظه در بانک هم ذخیره کنید , این روشی ک گفتم می تونید برید . ک در اینصورت نیازه همچین Wrapper ای رو هم ک خودتون ی نمونه پیدا کردین رو بنویسین.

اما اگه اینکه می گین تغییرات داده بشه و بعد از کلید "ذخیره", ساختار درخت در بانک ذخیره شه:
ب همچین Wrapper ای نیازی ندارین (ک البته چیز خوبی هم هس کلا یک کلاس TreeView سفارشی برای خودتون داشته باشید با این امکانات)
یک راه ساده اینه ک (در صورتی ک روابط جداول مربوط ب ساختار درختی شما رو با جداول دیگه بیخیال بشیم) , ابتدا محتوای 2تا جدول رو ب کل حذف کنید. سپس درخت رو در بانک ذخیره کنید از نو. اگه این روش رو بخواین برین , بعد از حذف , کافیه بصورت بازگشتی درخت رو پیمایش کنید و در هر بار پیمایش مقدار نود رو در بانک insert کنید.

اما اگه نخواین (ب هر دلیلی) جدول مربوط ب درخت رو خالی کنید کار یکم بیشتر می شه.
کدوم راه رو می خواین برین؟ و چرا؟

اگه راه اول رو بخواین برین ی بخش از پیاده سازیش رو براتون امروز فردا می ذارم

khokhan
سه شنبه 03 بهمن 1391, 00:31 صبح
اول از همه بابت توضحات خوبتون تشکر می کنم


دوم اینکه اگه لطفی کنین و همون راه اول رو پیاده سازیش رو بذارین سپاسگذارتان خواهم بود

RED-C0DE
سه شنبه 03 بهمن 1391, 14:05 عصر
بخش مربوط ب ذخیره وضعیت treeview در بانک رو نوشتم. امیدوارم روی لود و ساخت درخت ب مشکلی نخورین. اگه خوردین بگین دوستان راهنمایی کنن.

یک treeview روی فرم با چند نود.
یک جدول (MyTreeTable) بصورت :

Id int
ParentNodeId nullable int
NodeText NVarchar(50)


و یک SP برای عملیات درج نود در جدول.
اسکریپت بانک رو ابتدا اجرا کنید

khokhan
سه شنبه 03 بهمن 1391, 16:37 عصر
ممنونم دوست بسیار محترم

یه مشکلی که هست اینه که دیتابیس مورد استفاده من در برنامه " اکسس " هستش وشیوه استفاده شما برای "Save " با

oledb ایراد میده

سعی می کنم روشی برای برگردوندن سورس کد پیدا کنم

بازم ممنونم