Updating TreeView after changing CellRendererCombo (Gtk#) - c#

Could somebody point me in the right direction on how to update a Gtk.TreeView after changing a CellRendererCombo in Gtk#?
Since the only example I found was in Python, I tried to port the example to C#, but without success so far.
The Python example is here: http://learngtk.org/pygtk-tutorial/cellrenderercombo.html
In the code below I am having difficulties with the method ComboChanged.
After changing the value in the combobox (by selecting a different value) and placing the focus outside of the combobox, the value does not change.
using System;
using Gtk;
using System.Collections.Generic;
public partial class MainWindow: Gtk.Window
{
public MainWindow (): base (Gtk.WindowType.Toplevel)
{
Build ();
var tvComboBox = InitTreeViewWithComboBox ();
var vbox = new Gtk.VBox ();
vbox.PackStart (tvComboBox, true, true, 0);
this.Add (vbox);
this.ShowAll ();
}
// adopted from http://learngtk.org/pygtk-tutorial/cellrenderercombo.html
ListStore liststore_hardware;
ListStore liststore_manufacturers;
private TreeView InitTreeViewWithComboBox ()
{
liststore_manufacturers = new Gtk.ListStore(typeof (string));
var manufacturers = new List<string> {"Sony", "LG", "Panasonic", "Toshiba", "Nokia", "Samsung"};
foreach (var item in manufacturers) {
liststore_manufacturers.AppendValues (item);
}
liststore_hardware = new Gtk.ListStore(typeof (string), typeof (string));
liststore_hardware.AppendValues ("Television", "Samsung");
liststore_hardware.AppendValues ("Mobile Phone", "LG");
liststore_hardware.AppendValues ("DVD Player", "Sony");
var treeview = new Gtk.TreeView ();
treeview.Model = liststore_hardware;
var column_text = new TreeViewColumn { Title = "Text" };
var column_combo = new TreeViewColumn { Title = "Combo" };
treeview.AppendColumn (column_text);
treeview.AppendColumn (column_combo);
var cellrenderer_text = new CellRendererText ();
column_text.PackStart (cellrenderer_text, false);
column_text.AddAttribute (cellrenderer_text, "text", 0);
var cellrenderer_combo = new CellRendererCombo ();
cellrenderer_combo.Editable = true;
cellrenderer_combo.Model = liststore_manufacturers;
cellrenderer_combo.TextColumn = 0;
column_combo.PackStart (cellrenderer_combo, false);
column_combo.AddAttribute (cellrenderer_combo, "text", 1);
cellrenderer_combo.Edited += ComboChanged;
return treeview;
}
void ComboChanged (object o, EditedArgs args)
{
// Not really sure what to do here....
/*
var crc = o as CellRendererCombo;
TreeIter iter; // index within the combobox
if (!crc.Model.GetIterFirst (out iter)) {
return;
}
crc.Model.SetValue (iter, 0, args.NewText);
liststore_hardware.SetValue (iterHardware, 1, args.NewText);
*/
}

Going through the API a bit more I found the solution...:
void ComboChanged (object o, EditedArgs args)
{
TreeSelection selection = treeview.Selection;
TreeIter iter;
if (!selection.GetSelected (out iter)) {
return;
}
liststore_hardware.SetValue (iter, 1, args.NewText);
}

Related

Looking way to refactor these two methods into single method

Hi All i am trying to generate the word document with two different tables included in it, for this purpose i have two similar methods where i am passing word document reference and data object and table to the similar methods..
Now i am looking to make single method in generic way so that in different places i can use single method by passing parameters to it
Method 1 :
private static List<OpenXmlElement> RenderExhaustEquipmentTableDataAndNotes(MainDocumentPart mainDocumentPart, List<ProjectObject<ExhaustEquipment>> exhaustEquipment,Table table)
{
HtmlConverter noteConverter = new HtmlConverter(mainDocumentPart);
var equipmentExhaustTypes = new Dictionary<string, List<ProjectObject<ExhaustEquipment>>>();
foreach (var item in exhaustEquipment)
{
string exhaustEquipmentName = item.TargetObject.Name;
if (!equipmentExhaustTypes.ContainsKey(exhaustEquipmentName))
{
equipmentExhaustTypes.Add(exhaustEquipmentName, new List<ProjectObject<ExhaustEquipment>>());
}
equipmentExhaustTypes[exhaustEquipmentName].Add(item);
}
List<OpenXmlElement> notes = new List<OpenXmlElement>();
int noteIndex = 1;
foreach (var exhaustEquipmentItem in equipmentExhaustTypes)
{
List<string> noteIndices = new List<string>();
for (int exhaustEquipmentConditionIndex = 0; exhaustEquipmentConditionIndex < exhaustEquipmentItem.Value.Count; exhaustEquipmentConditionIndex++)
{
var condition = exhaustEquipmentItem.Value[exhaustEquipmentConditionIndex];
var row = new TableRow();
Run superscriptRun = new Run(new RunProperties(new VerticalTextAlignment { Val = VerticalPositionValues.Superscript }));
if (exhaustEquipmentConditionIndex == 0)
{
row.Append(RenderOpenXmlElementContentCell(new Paragraph(
new List<Run> {
new Run(new RunProperties(), new Text(exhaustEquipmentItem.Key) { Space = SpaceProcessingModeValues.Preserve }),
superscriptRun
}), 1,
new OpenXmlElement[] {new VerticalMerge { Val = MergedCellValues.Restart },new TableCellMargin {
LeftMargin = new LeftMargin { Width = "120" },
TopMargin = new TopMargin { Width = "80" } }
}));
}
else
{
row.Append(RenderTextContentCell(null, 1, null, null, new OpenXmlElement[] { new VerticalMerge { Val = MergedCellValues.Continue } }));
}
row.Append(RenderTextContentCell(condition.TargetObject.IsConstantVolume ? "Yes" : "No"));
row.Append(RenderTextContentCell($"{condition.TargetObject.MinAirflow:R2}"));
row.Append(RenderTextContentCell($"{condition.TargetObject.MaxAirflow:R2}"));
if (condition.TargetObject.NotesHTML?.Count > 0)
{
foreach (var note in condition.TargetObject.NotesHTML)
{
var compositeElements = noteConverter.Parse(note);
var htmlRuns = compositeElements.First().ChildElements.Where(c => c is Run).Cast<Run>().Select(n => n.CloneNode(true));
notes.Add(new Run(htmlRuns));
noteIndices.Add(noteIndex++.ToString(CultureInfo.InvariantCulture));
}
}
if (exhaustEquipmentConditionIndex == exhaustEquipmentItem.Value.Count - 1 && condition.TargetObject.NotesHTML?.Count > 0)
{
superscriptRun.Append(new Text($"({String.Join(',', noteIndices)})") { Space = SpaceProcessingModeValues.Preserve });
}
table.Append(row);
}
}
List<OpenXmlElement> notesSection = new List<OpenXmlElement>();
List<OpenXmlElement> result = RenderNotesArray(table, notes, notesSection);
return result;
}
and I am calling this method like this in below
var table = new Table(RenderTableProperties());
table.Append(new TableRow(
RenderTableHeaderCell("Name"),
RenderTableHeaderCell("Constant Volume"),
RenderTableHeaderCell("Minimum Airflow", units: "(cfm)"),
RenderTableHeaderCell("Wet Bulb Temperature", units: "(cfm)")
));
body.Append(RenderExhaustEquipmentTableDataAndNotes(mainDocumentPart, designHubProject.ExhaustEquipment, table));
Method 2:
private static List<OpenXmlElement> RenderInfiltrationTableData(MainDocumentPart mainDocumentPart, List<ProjectObject<Infiltration>> infiltration,Table table)
{
HtmlConverter noteConverter = new HtmlConverter(mainDocumentPart);
var nameByInflitrationObject = new Dictionary<string, List<ProjectObject<Infiltration>>>();
foreach (var infiltrationData in infiltration)
{
string infiltrationName = infiltrationData.TargetObject.Name;
if (!nameByInflitrationObject.ContainsKey(infiltrationName))
{
nameByInflitrationObject.Add(infiltrationName, new List<ProjectObject<Infiltration>>());
}
nameByInflitrationObject[infiltrationName].Add(infiltrationData);
}
List<OpenXmlElement> notes = new List<OpenXmlElement>();
int noteIndex = 1;
foreach (var inflitrationDataItem in nameByInflitrationObject)
{
List<string> noteIndices = new List<string>();
for (int inflitrationNameIndex = 0; inflitrationNameIndex < inflitrationDataItem.Value.Count; inflitrationNameIndex++)
{
var dataItem = inflitrationDataItem.Value[inflitrationNameIndex];
var row = new TableRow();
Run superscriptRun = new Run(new RunProperties(new VerticalTextAlignment { Val = VerticalPositionValues.Superscript }));
if (inflitrationNameIndex == 0)
{
row.Append(RenderOpenXmlElementContentCell(new Paragraph(
new List<Run> {
new Run(new RunProperties(), new Text(inflitrationDataItem.Key) { Space = SpaceProcessingModeValues.Preserve }),superscriptRun
}), 1,
new OpenXmlElement[] {new VerticalMerge { Val = MergedCellValues.Restart },new TableCellMargin {
LeftMargin = new LeftMargin { Width = "120" },
TopMargin = new TopMargin { Width = "80" }}
}));
}
else
{
row.Append(RenderTextContentCell(null, 1, null, null, new OpenXmlElement[] { new VerticalMerge { Val = MergedCellValues.Continue } }));
}
row.Append(RenderTextContentCell($"{dataItem.TargetObject.AirflowScalar.ToString("R2", CultureInfo.CurrentCulture)} cfm {EnumUtils.StringValueOfEnum(dataItem.TargetObject.InfiltrationCalculationType).ToLower(CultureInfo.CurrentCulture)}"));
if (dataItem.TargetObject.NotesHTML?.Count > 0)
{
foreach (var note in dataItem.TargetObject.NotesHTML)
{
var compositeElements = noteConverter.Parse(note);
var htmlRuns = compositeElements.First().ChildElements.Where(c => c is Run).Cast<Run>().Select(n => n.CloneNode(true));
notes.Add(new Run(htmlRuns));
noteIndices.Add(noteIndex++.ToString(CultureInfo.InvariantCulture));
}
}
if (inflitrationNameIndex == inflitrationDataItem.Value.Count - 1 && dataItem.TargetObject.NotesHTML?.Count > 0)
{
superscriptRun.Append(new Text($"({String.Join(',', noteIndices)})") { Space = SpaceProcessingModeValues.Preserve });
}
table.Append(row);
}
}
List<OpenXmlElement> notesSection = new List<OpenXmlElement>();
List<OpenXmlElement> result = RenderNotesArray(table, notes, notesSection);
return result;
}
and then i am calling this method here like as below
var table = new Table(RenderTableProperties());
table.Append(new TableRow(
RenderTableHeaderCell("Type"),
RenderTableHeaderCell("Air Flow")
));
body.Append(RenderInfiltrationTableData(mainDocumentPart, designHubProject.Infiltration, table));
i know these are lots of lines but is there any generic way to use single method out of these two similar methods and i am using .net core
Could any one please suggest any idea or suggestion how can i refactor these two methods into single method that would be very grateful.
many thanks in advance
Before we can create a single function that handles both types, achieving the highly laudable goal of removing gratuitous duplication, we should clean the code up to make it easier to see which parts, if any, are different between the two nearly identical methods. And there is a lot to clean up, even if we only had one function.
In short, your functions are too long, having too much much code in one place, and in fact too much code altogether.
In the following, the original code has been broken down into multiple functions with specific purposes and refactored to remove DIY nonsense in favor of the standard library functions and the removal of pointless code.
static IEnumerable<OpenXmlElement> RenderExhaustEquipmentTableDataAndNotes(MainDocumentPart mainDocumentPart, List<ProjectObject<ExhaustEquipment>> exhaustEquipment, Table table)
{
var equipmentByType = exhaustEquipment.ToLookup(item => item.TargetObject.Name);
List<OpenXmlElement> notes = new List<OpenXmlElement>();
foreach (var items in equipmentByType)
{
Run superscriptRun = CreateSuperScriptRun();
foreach (var item in items)
{
var row = new TableRow();
if (item == items.First())
{
row.Append(CreateFirstRowStartingCell(items.Key, superscriptRun));
}
else
{
row.Append(RenderTextContentCell(null, 1, null, null, new[] {
new VerticalMerge { Val = MergedCellValues.Continue }
}));
}
row.Append(RenderTextContentCell(item.TargetObject.IsConstantVolume ? "Yes" : "No"));
row.Append(RenderTextContentCell($"{item.TargetObject.MinAirflow:R2}"));
row.Append(RenderTextContentCell($"{item.TargetObject.MaxAirflow:R2}"));
table.Append(row);
var itemNotes = ParseNotes(mainDocumentPart, item.TargetObject.NotesHTML);
if (item == items.Last() && itemNotes.Any())
{
UpdateSuperScript(superscriptRun, itemNotes);
}
notes.AddRange(itemNotes);
}
}
List<OpenXmlElement> result = RenderNotesArray(table, notes, new List<OpenXmlElement>());
return result;
}
private static Run CreateSuperScriptRun()
{
return new Run(new RunProperties(new VerticalTextAlignment
{
Val = VerticalPositionValues.Superscript
}));
}
private static void UpdateSuperScript(Run superscriptRun, IEnumerable<OpenXmlElement> notes)
{
superscriptRun.Append(new Text($"({string.Join(",", Enumerable.Range(0, notes.Count()))})")
{
Space = SpaceProcessingModeValues.Preserve
});
}
private static IEnumerable<OpenXmlElement> ParseNotes(MainDocumentPart mainDocumentPart, IEnumerable<OpenXmlElement> notes)
{
return notes == null
? Enumerable.Empty<OpenXmlElement>()
: notes.Select(note => new HtmlConverter(mainDocumentPart).Parse(note))
.Select(note => note.First().ChildElements
.OfType<Run>()
.Select(n => n.CloneNode(true))).Select(htmlRuns => new Run(htmlRuns))
.ToList();
}
private OpenXmlElement CreateFirstRowStartingCell(string key, Run superscriptRun)
{
return RenderOpenXmlElementContentCell(
new Paragraph(new List<Run> {
new Run(new RunProperties(), new Text(key) { Space = SpaceProcessingModeValues.Preserve }),
superscriptRun
}),
1,
new OpenXmlElement[] {
new VerticalMerge { Val = MergedCellValues.Restart },
new TableCellMargin { LeftMargin = new LeftMargin { Width = "120" }, TopMargin = new TopMargin { Width = "80" } }
});
}
Now, let's tackle the second function:
static IEnunumerable<OpenXmlElement> RenderInfiltrationTableData(MainDocumentPart mainDocumentPart, IEnunumerable<ProjectObject<Infiltration>> infiltration, Table table)
{
var infiltrationsByType = infiltration.ToLookup(item => item.TargetObject.Name);
List<OpenXmlElement> notes = new List<OpenXmlElement>();
foreach (var inflitrations in infiltrationsByType)
{
Run superscriptRun = CreateSuperScriptRun();
foreach (var item in inflitrations)
{
var row = new TableRow();
if (item == inflitrations.First())
{
row.Append(CreateFirstRowStartingCell(inflitrations.Key, superscriptRun));
}
else
{
row.Append(RenderTextContentCell(null, 1, null, null, new[] {
new VerticalMerge { Val = MergedCellValues.Continue }
}));
}
row.Append(RenderTextContentCell($"{item.TargetObject.AirflowScalar:R2} cfm {item.TargetObject.InfiltrationCalculationType}").ToLower());
table.Append(row);
var itemNotes = ParseNotes(mainDocumentPart, item.TargetObject.NotesHTML);
if (item == inflitrations.Last() && itemNotes.Any())
{
UpdateSuperScript(superscriptRun, itemNotes);
}
notes.AddRange(itemNotes);
}
}
IEnumerable<OpenXmlElement> result = RenderNotesArray(table, notes, new List<OpenXmlElement>());
return result;
}
As we have seen, duplication can be massively reduced simply by extracting code into simple helper functions.
This also makes it far easier to see just where the differences are between the two functions.
It is simply a matter of
row.Append(RenderTextContentCell(item.TargetObject.IsConstantVolume ? "Yes" : "No"));
row.Append(RenderTextContentCell($"{item.TargetObject.MinAirflow:R2}"));
row.Append(RenderTextContentCell($"{item.TargetObject.MaxAirflow:R2}"));
vs.
row.Append(RenderTextContentCell($"{item.TargetObject.AirflowScalar:R2} cfm {item.TargetObject.InfiltrationCalculationType}").ToLower());
To achieve your desired goal of a single function, we can make a generic function, and require that the caller pass in a function that will take care of these differences.
static IEnumerable<OpenXmlElement> RenderTableDataAndNotes<T>(
MainDocumentPart mainDocumentPart,
IEnumerable<ProjectObject<T>> projects,
Table table,
Func<ProjectObject<T>, IEnumerable<OpenXmlElement>> createCells
) where T : ITargetObject
{
var projectsByType = projects.ToLookup(item => item.TargetObject.Name);
List<OpenXmlElement> notes = new List<OpenXmlElement>();
foreach (var items in projectsByType)
{
Run superscriptRun = CreateSuperScriptRun();
foreach (var item in items)
{
var row = new TableRow();
if (item == items.First())
{
row.Append(CreateFirstRowStartingCell(items.Key, superscriptRun));
}
else
{
row.Append(RenderTextContentCell(null, 1, null, null, new[] {
new VerticalMerge { Val = MergedCellValues.Continue }
}));
}
var itemCells = createCells(item);
foreach (var cell in itemCells)
{
row.Append(cell);
}
table.Append(row);
var itemNotes = ParseNotes(mainDocumentPart, item.TargetObject.NotesHTML);
if (item == items.Last() && itemNotes.Any())
{
UpdateSuperScript(superscriptRun, itemNotes);
}
notes.AddRange(itemNotes);
}
}
IEnumerable<OpenXmlElement> result = RenderNotesArray(table, notes, new List<OpenXmlElement>());
return result;
}
Now, when we call it for say some Exhaust Equipment, we do so as follows:
var rendered = RenderTableDataAndNotes(mainDocumentPart, exhaustProjects, table,
exhaust => new[] {
RenderTextContentCell(exhaust.TargetObject.IsConstantVolume ? "Yes" : "No"),
RenderTextContentCell($"{exhaust.TargetObject.MinAirflow:R2}"),
RenderTextContentCell($"{exhaust.TargetObject.MaxAirflow:R2}"),
});
And for infiltration projects, we would do as follows:
var rendered = RenderTableDataAndNotes(
mainDocumentPart,
infiltrationProjects,
table,
infiltration => new[] {
RenderTextContentCell($"{item.TargetObject.AirflowScalar:R2} cfm {item.TargetObject.InfiltrationCalculationType}")
.ToLower()
});
The code could still be substantially improved even now. Currently it requires that the various project types implement a common ITargetObject interface declaring the Name property used to group projects by type. If you refactored your code to reduce nesting by hoisting Name to the ProjectObject<T> type, then we could remove the constraint and the otherwise useless requirement that Infiltration and ExhaustEquipment implement the ITargetObject interface.
Note, if you can't change the types, you can adjust the code in a few ways.
For example, you can remove the type constraint on T and build the lookup outside and pass it to the function:
static IEnumerable<OpenXmlElement> RenderTableDataAndNotes<T>(
MainDocumentPart mainDocumentPart,
ILookup<string, ProjectObject<T>> projectsByType,
Table table,
Func<ProjectObject<T>, IEnumerable<OpenXmlElement>> createCells
)
Then you would call it as
var infiltrationProjectsByType = infiltrationProjects.ToLookup(project => project.Name);
var rendered = RenderTableDataAndNotes(
mainDocumentPart,
infiltrationProjectsByType,
table,
infiltration => new[] {
RenderTextContentCell($"{infiltration.TargetObject.AirflowScalar:R2} cfm {infiltration.TargetObject.InfiltrationCalculationType}").ToLower()
}
);

GTK# TreeViewColumn with multiple renderers

I am working at chat with stickers on Mono C# with GTK# GUI. While googling i have founded that best widget for this purpose is TextView. Problem in creating ListStore for column with multiple renderers:
TreeView messageLog=new TreeView();
#Creating a column
TreeViewColumn messageColumn = new TreeViewColumn();
messageLog.AppendColumn(messageColumn);
#Creating model for tree
ListStore messageStore = new ListStore(typeof(string), typeof(Gdk.Pixbuf));
messageLog.Model = messageStore;
#Packing 2 renderers into column
CellRendererText textCell = new CellRendererText();
messageColumn.PackEnd(textCell, true);
messageColumn.AddAttribute(textCell, "text", 0);
CellRendererPixbuf stickerCell = new CellRendererPixbuf();
messageColumn.PackStart(stickerCell, false);
messageColumn.AddAttribute(stickerCell, "pixbuf", 1);
Add text to TreeView:
messageStore.AppendValues(value);
This way, i can add pictures to TreeView only with:
messageStore.AppendValues(null, new Gdk.Pixbug("red.png");
But it shows like in new column. Is there a better way to define ListStore or append values so picture will be shown in same column as message? Or maybe there is a better widget for this purpose?
You were right, I've been able to do it (more than one item in a single column), following (or inferring from) the Mono's Gtk# tree view tutorial.
namespace TreeViewMultipleRenderers
{
public class MainWindowView: Gtk.Window
{
public MainWindowView()
: base( Gtk.WindowType.Toplevel )
{
this.SetGeometryHints( this,
new Gdk.Geometry { MinHeight = 400, MinWidth = 600 },
Gdk.WindowHints.MinSize );
this.Build();
}
void BuildIcons()
{
this.IconYes = new Gdk.Pixbuf(
System.Reflection.Assembly.GetEntryAssembly(),
"TreeViewMultipleRenderers.Res.yes.png", 16, 16 );
this.IconNo = new Gdk.Pixbuf(
System.Reflection.Assembly.GetEntryAssembly(),
"TreeViewMultipleRenderers.Res.no.png", 16, 16 );
}
Gtk.TreeView BuildTreeView()
{
var toret = new Gtk.TreeView();
// Index column
var columnIndex = new Gtk.TreeViewColumn {
Title = "#"
};
var indexRenderer = new Gtk.CellRendererText();
columnIndex.PackStart( indexRenderer, expand: true );
columnIndex.AddAttribute( indexRenderer, "text", 0 );
// Data column
var columnData = new Gtk.TreeViewColumn {
Title = "Mixed column"
};
var dataRenderer1 = new Gtk.CellRendererPixbuf();
columnData.PackStart( dataRenderer1, expand: false );
columnData.AddAttribute( dataRenderer1, "pixbuf", 1 );
var dataRenderer2 = new Gtk.CellRendererText();
columnData.PackStart( dataRenderer2, expand: true );
columnData.AddAttribute( dataRenderer2, "text", 2 );
toret.AppendColumn( columnIndex );
toret.AppendColumn( columnData );
// Model
var store = new Gtk.ListStore( typeof( string ), typeof( Gdk.Pixbuf ), typeof( string ) );
toret.Model = store;
store.AppendValues( "1", this.IconYes, "works" );
store.AppendValues( "2", this.IconNo, "does not work" );
return toret;
}
void Build()
{
this.BuildIcons();
this.TreeView = this.BuildTreeView();
this.Add( this.TreeView );
}
public Gtk.TreeView TreeView {
get; set;
}
public Gdk.Pixbuf IconYes {
get; private set;
}
public Gdk.Pixbuf IconNo {
get; private set;
}
}
}
I can't see any important difference with the relevant parts of the code you posted, so I guess the problem must be elsewhere. You can find this code in the GtkTreeViewMultipleRenderers Github repository. I've also uploaded an executable in the releases section of the repository.
Hope this helps.

How to list all the rows of a Gtk.Treeview

I'm having trouble grasping the use of GTK's TreeView and so I am running through some examples I have found online. Putting them together I am unable to work out how to extract the information in the view. I am operating the TreeView as a simple List with columns for the presentation of tabled data.
I am getting confused between the roles of models, stores and the actual treeview itself.
I am working from the example here
using System;
using System.Collections;
using Gtk;
public class Actress
{
public string Name;
public string Place;
public int Year;
public Actress(string name, string place, int year)
{
Name = name;
Place = place;
Year = year;
}
}
public class SharpApp : Window
{
ListStore store;
Statusbar statusbar;
enum Column
{
Name,
Place,
Year
}
Actress[] actresses =
{
new Actress("Jessica Alba", "Pomona", 1981),
new Actress("Sigourney Weaver", "New York", 1949),
new Actress("Angelina Jolie", "Los Angeles", 1975),
new Actress("Natalie Portman", "Jerusalem", 1981),
new Actress("Rachel Weissz", "London", 1971),
new Actress("Scarlett Johansson", "New York", 1984)
};
public SharpApp() : base ("ListView")
{
BorderWidth = 8;
SetDefaultSize(350, 250);
SetPosition(WindowPosition.Center);
DeleteEvent += delegate { Application.Quit(); };
VBox vbox = new VBox(false, 8);
ScrolledWindow sw = new ScrolledWindow();
sw.ShadowType = ShadowType.EtchedIn;
sw.SetPolicy(PolicyType.Automatic, PolicyType.Automatic);
vbox.PackStart(sw, true, true, 0);
store = CreateModel();
TreeView treeView = new TreeView(store);
treeView.RulesHint = true;
treeView.RowActivated += OnRowActivated;
sw.Add(treeView);
AddColumns(treeView);
statusbar = new Statusbar();
vbox.PackStart(statusbar, false, false, 0);
Add(vbox);
ShowAll();
}
void OnRowActivated (object sender, RowActivatedArgs args) {
TreeIter iter;
TreeView view = (TreeView) sender;
if (view.Model.GetIter(out iter, args.Path)) {
string row = (string) view.Model.GetValue(iter, (int) Column.Name );
row += ", " + (string) view.Model.GetValue(iter, (int) Column.Place );
row += ", " + view.Model.GetValue(iter, (int) Column.Year );
statusbar.Push(0, row);
}
// *** if I can dump treeview to the console here I'll be happy ***
// *** I'd prefer a foreach or do/while ***
}
void AddColumns(TreeView treeView)
{
CellRendererText rendererText = new CellRendererText();
TreeViewColumn column = new TreeViewColumn("Name", rendererText,
"text", Column.Name);
column.SortColumnId = (int) Column.Name;
treeView.AppendColumn(column);
rendererText = new CellRendererText();
column = new TreeViewColumn("Place", rendererText,
"text", Column.Place);
column.SortColumnId = (int) Column.Place;
treeView.AppendColumn(column);
rendererText = new CellRendererText();
column = new TreeViewColumn("Year", rendererText,
"text", Column.Year);
column.SortColumnId = (int) Column.Year;
treeView.AppendColumn(column);
}
ListStore CreateModel()
{
ListStore store = new ListStore( typeof(string),
typeof(string), typeof(int) );
foreach (Actress act in actresses) {
store.AppendValues(act.Name, act.Place, act.Year );
}
return store;
}
public static void Main()
{
Application.Init();
new SharpApp();
Application.Run();
}
}
An array of actresses is created at the top of the code, and that is used to populate a ListStore (towards the end of the code) that is in turn used to populate the TreeView.
I want to iterate over the treeview's data and update the columns within it.
To this end, since OnRowActivated is a nice place to dump information, I'd like to dump out the entire treeview contents to the console when a node is selected.
Any help in guiding me to get this done (and hopefully understanding it in the process) would be appreciated.
Whilst certainly not elegant I found the answer which also updates some of the data in the model. I'd be happy if anyone could improve upon this.
TreeIter iter;
TreeModel model = tv.Model;
if (model.GetIterFirst(out iter))
{
do
{
newYear = ModifyYear((string)model.GetValue(iter, (int)Column.Year));
Console.WriteLine("Updating: {0} -- {1} {2}",
(string)model.GetValue(iter, (int)Column.Name),
model.GetValue(iter, (int)Column.Year),
newYear );
model.SetValue(iter, (int)Column.Year, (int)newYear);
} while (model.IterNext(ref iter));
}

Adding subsequent months on x-axis using Live Charts

private int SumOfNodes()
{
ManufacturingDataModel MDM = new ManufacturingDataModel();
Test t = new Test(MDM);
List<Hardware> SumOfHardware = t.GetHardware().FindAll(x => x.Nodes != 0);
int SumOfNodes = 0;
foreach (Hardware i in SumOfHardware )
{
SumOfNodes += i.Nodes;
}
return SumOfNodes;
}
private int SumOfRepeaters()
{
ManufacturingDataModel MDM = new ManufacturingDataModel();
Test t = new Test(MDM);
List<Hardware> SumOfHardware = t.GetHardware().FindAll(x => x.Repeaters != 0);
int SumOfRepeaters = 0;
foreach (Hardware i in SumOfHardware)
{
SumOfRepeaters += i.Repeaters;
}
return SumOfRepeaters;
}
private int SumOfHubs()
{
ManufacturingDataModel MDM = new ManufacturingDataModel();
Test t = new Test(MDM);
List<Hardware> SumOfHardware = t.GetHardware().FindAll(x => x.Hubs != 0);
int SumOfHubs= 0;
foreach (Hardware i in SumOfHardware)
{
SumOfHubs += i.Hubs;
}
return SumOfHubs;
}
private string Month()
{
DateTime now = DateTime.Now;
string month = DateTime.Now.ToString("MMMM");
return month;
}
private void DisplayData()
{
SeriesCollection = new SeriesCollection
{
new ColumnSeries
{
Title = "Nodes",
Values = new ChartValues<int> { SumOfNodes() }
},
};
SeriesCollection.Add
(
new ColumnSeries
{
Title = "Repeaters",
Values = new ChartValues<int> { SumOfRepeaters() }
}
);
SeriesCollection.Add
(
new ColumnSeries
{
Title = "Hubs",
Values = new ChartValues<int> { SumOfHubs() }
}
);
Labels = new[] { Month() };
Formatter = value => value.ToString("N");
DataContext = this;
}
enter image description here
At this points I've managed to create an app that adds/removes and updates my items on my database. I'm also planning to add some stats (Started off with graph visualisation) but I'm facing an issue.
I want to seperate columns based on months. So for example as seen by the image attached no matter how many items i add , remove or update the total amount for each item is added to Decemeber. But when January comes any newly added modification to the quantity of my items I would like to see adjacent to the Decemeber one.
P.S.: There is alot of code repitition which will accounted for later on.
I've managed to put something together regarding my answer above which may be usefull for someone in the future
What I've done was to
1) Get my data from my db.
2) Find the Month of interest using LINQ queries **
instead of selecting the items (i.e repeaters, nodes)
**
3) By doing so it also accounts for the items I'm interested in during the month of interest.
4) Do an incremental foreach loop as shown in my code above.
5) Change the methodd i want to display (i.e DisplayData to DisplayDecemberData) and use in the same way as above.
6) Create further methods for the subsequent months and just add the additional information in the ChartValues object.
See Below
private int DecemberNodes()
{
ManufacturingDataModel MDM = new ManufacturingDataModel();
Test t = new Test(MDM);
List<Hardware> decembernodes = t.GetHardware().FindAll(x => x.Date.Month==12);
int DecemberSumOfNodes = 0;
foreach (Hardware i in decembernodes)
{
DecemberSumOfNodes += i.Nodes;
}
return DecemberSumOfNodes;
}
private int JanuaryNodes()
{
ManufacturingDataModel MDM = new ManufacturingDataModel();
Test t = new Test(MDM);
List<Hardware> januarynodes = t.GetHardware().FindAll(x => x.Date.Month==01);
int JanuarySumOfNodes = 0;
foreach (Hardware i in januarynodes)
{
JanuarySumOfNodes += i.Nodes;
}
private void DisplayDecemberData()
{
SeriesCollection = new SeriesCollection
{
new ColumnSeries
{
Title = "Nodes",
Values = new ChartValues<int> { DecemberNodes() }
},
};
private void DisplayJanuaryData()
{
SeriesCollection = new SeriesCollection
{
new ColumnSeries
{
Title = "Nodes",
**Values = new ChartValues<int> { DecemberNodes(), JanuaryNodes() }**
},
};
There is some code repetition and I'm pretty sure there is a more code concise way of actually doing it but for now this seems to do the trick. When I simplify the code i will post it.

Optimizing large switch statement

I have large switch statement in which I create UIElements based on input value from XElement:
public static UIElement CreateElement(XElement element) {
var name = element.Attribute("Name").Value;
var text = element.Attribute("Value").Value;
var width = Convert.ToDouble(element.Attribute("Width").Value);
var height = Convert.ToDouble(element.Attribute("Height").Value);
//...
switch (element.Attribute("Type").Value) {
case "System.Windows.Forms.Label":
return new System.Windows.Controls.Label() {
Name = name,
Content = text,
Width = width,
Height = height
};
case "System.Windows.Forms.Button":
return new System.Windows.Controls.Button() {
Name = name,
Content = text,
Width = width,
Height = height
};
//...
default:
return null;
}
}
I am creating a lot controls like this and as you can see, too much repetition is going on.
Is there some way to avoid this repetition? Thanks in advance for ideas.
You could create a generic function that does the create:
private static Create<T>(string name, string text, double width, double height) where T: Control, new()
{
return new T { Name = name, Content = text, Width = width, Height = height }
}
Your switch then becomes:
switch (element.Attribute("Type").Value) {
case "System.Windows.Forms.Label" : return Create<System.Windows.Forms.Label>(name, text, width, height);
etc.
}
You could also adapt this to pass in the XElement, whichever you prefer.
If the Type attribute is always the name of the System.Type you want, then you could just do
Control ctrl = (Control) Activator.CreateInstance(Type.GetType(element.Attribute("Type").Value));
ctrl.Name = name;
etc.
If there's a one to one mapping between the value of the attribute and the type you want, then you can declare a readonly static field with the mapping:
private static readonly uiTypeMapping = new Dictionary<string,Type> {
{ "System.Windows.Forms.Label", typeof(System.Windows.Controls.Label) },
{ "System.Windows.Forms.Button", typeof(System.Windows.Controls.Button) },
{ etc. }
};
And use
UIElement elem = (UIElement) Activator.CreateInstance(uiTypeMapping[element.Attribute("Type").Value]);
etc.
Something like this could work... :)
var controlCreators = new Dictionary<string, Func<ContentControl>>
{
{"System.Windows.Forms.Label", () => new Label()},
{"System.Windows.Forms.Button", () => new Button()}
};
Func<ContentControl> createControl;
if (!controlCreators.TryGetValue(element.Attribute("Type").Value, out createControl))
{
return null;
}
var control = createControl();
control.Name = name;
control.Content = text;
control.Width = width;
control.Height = height;
return control;
Those different controls have inheritance trees. So for example Width, Height, Name are defined on FrameworkElement. So you could do something like the following:
object createdObject = null;
switch (element.Attribute("Type").Value)
{
case "System.Windows.Forms.Label":
createdObject = new System.Windows.Controls.Label();
break;
case "System.Windows.Forms.Button":
createdObject = new System.Windows.Controls.Button();
break;
}
var fe = createdObject as FrameworkElement;
if (fe != null)
{
fe.Name = element.Attribute("Name").Value;
fe.Width = Convert.ToDouble(element.Attribute("Width").Value);
fe.Height = Convert.ToDouble(element.Attribute("Height").Value);
}
var ce = createdObject as ContentElement;
if (ce != null)
{
ce.Content = element.Attribute("Value").Value;
}
return createdObject;
Note that by using this approach, in comparison to Flynn's answer, you can also easily add code such as "when the control is an ItemsControl, do this", i.e. code which won't apply to every type, but only to some of them.
You can do it with reflection + expressions.
[TestClass]
public class UnitTest1
{
public class Creator
{
private static Dictionary<string,Func<XElement, Control>> _map = new Dictionary<string, Func<XElement,Control>>();
public static Control Create(XElement element)
{
var create = GetCreator(element.Attribute("Type").Value);
return create(element);
}
private static Expression<Func<XElement, string>> CreateXmlAttributeAccessor(string elementName)
{
return (xl => xl.Attributes(elementName).Select(el => el.Value).FirstOrDefault() ?? "_" + elementName);
}
private static Func<XElement, Control> GetCreator(string typeName)
{
Func<XElement, Control> existing;
if (_map.TryGetValue(typeName, out existing))
return existing;
// mapping for whatever property names you wish
var propMapping = new[]
{
new{ Name = "Name", Getter = CreateXmlAttributeAccessor("Name") },
new{ Name = "Content", Getter = CreateXmlAttributeAccessor("Value") },
};
var t = Assembly.GetAssembly(typeof (Control)).GetType("System.Windows.Controls." + typeName);
var elementParameter = Expression.Parameter(typeof (XElement), "element");
var p = from propItem in propMapping
let member = t.GetMember(propItem.Name)
where member.Length != 0
select (MemberBinding)Expression.Bind(member[0], Expression.Invoke(propItem.Getter, elementParameter));
var expression = Expression.Lambda<Func<XElement, Control>>(
Expression.MemberInit(Expression.New(t),p), elementParameter);
existing = expression.Compile();
_map[typeName] = existing;
return existing;
}
}
[TestMethod]
public void TestMethod1()
{
var xel = new XElement("control",
new XAttribute("Type", "Button"),
new XAttribute("Name", "Foo"),
new XAttribute("Value", "Bar"),
new XElement("NonExistent", "foobar")); // To check stability
var button = (Button) Creator.Create(xel);
Assert.AreEqual("Foo", button.Name);
Assert.AreEqual("Bar", button.Content);
}
}
To make it work with other types then string, you can use Expression.Convert. Left as an exercise.
You could to this with reflection instead, or you could create a Dictionary of strings (what you're switching on now) and Funcs (or actions rather) where you create the controls.
for the specific code you posted, you can assign height and width after the switch statement, since they exsist on Control directly.

Categories