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));
}
Related
I am having difficulties with autoscaling with a c# WinForms chart. I thought by setting the Minimum and Maximum properties of ChartArea.AxisX it would take care of the scaling for me.
In the example below I show a column chart that tracks the number of accidents that occur in a workplace over a given year. Each month, an email will be sent out to the managers of the workplace showing them an updated chart with that month's entries added to it. When the first email is sent out, the columns are very large. Each month, as more workplace accidents occur, the columns of the chart begin to shrink.
In other words, if you make the fakeAccidentsData array have just one or two elements, then the columns are massive. If you have more elements in fakeAccidentsData then they are smaller. I want the columns in this chart to remain a fixed, narrow width so that as more elements are added the columns won't change in size. So there should be enough room to accomodate 365 columns (one for each day of the year). If 365 columns are too small to be seen then I am willing to change it to go by week instead (so I would need space for 52 columns in that case).
Can this be accomplished?
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms.DataVisualization.Charting;
namespace MinimalExample
{
class WorkplaceAccident
{
public DateTime Timestamp;
}
class ChartExample
{
private readonly Series _series;
private readonly ChartArea _chartArea;
private readonly Chart _chart;
public IEnumerable<WorkplaceAccident> Accidents
{
set
{
var accidentCount = new Dictionary<DateTime, int>();
foreach (var accident in value)
{
DateTime accidentDate = accident.Timestamp.Date;
if (accidentCount.ContainsKey(accidentDate))
{
++accidentCount[accidentDate];
}
else accidentCount.Add(accidentDate, 1);
}
foreach(KeyValuePair<DateTime, int> pair in accidentCount)
{
Console.WriteLine("[Debug info] Adding " + pair.Key + ", " + pair.Value);
_series.Points.AddXY(pair.Key, pair.Value);
}
}
}
public ChartExample(int year)
{
_series = new Series();
_series.ChartType = SeriesChartType.Column;
_chartArea = new ChartArea();
_chartArea.AxisY.Interval = 1;
_chartArea.AxisY.IsMarginVisible = false;
_chartArea.AxisY.Title = "Number of workplace accidents";
_chartArea.AxisX.MajorGrid.Enabled = false;
_chartArea.AxisX.Minimum = new DateTime(year, 1, 1).ToOADate();
_chartArea.AxisX.Maximum = new DateTime(year, 12, 31).ToOADate();
_chart = new Chart();
_chart.ChartAreas.Add(_chartArea);
_chart.Series.Add(_series);
_chart.Size = new Size(800, 400);
}
public void SaveAsPng(string filename)
{
_chart.SaveImage(filename, ChartImageFormat.Png);
}
public static void Main()
{
WorkplaceAccident[] fakeAccidentsData = new WorkplaceAccident[]
{
new WorkplaceAccident { Timestamp = DateTime.Now.AddDays(-1) },
new WorkplaceAccident { Timestamp = DateTime.Now.AddDays(-1) },
new WorkplaceAccident { Timestamp = DateTime.Now.AddDays(-5) },
new WorkplaceAccident { Timestamp = DateTime.Now.AddMonths(-1) }
};
var chart = new ChartExample(2020) { Accidents = fakeAccidentsData };
chart.SaveAsPng(#"C:\Users\Home\Documents\chart.png");
Console.Write("Press any key to terminate... "); Console.ReadKey();
}
}
}
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.
I have a Gtk# TreeView with one visible column whose Model contains 3 values (text for the visible column, a value to sort the tree on and a reference to the base object). Here is the code:
internal class JpgTreeView : TreeView
{
private readonly ListStore _store = new ListStore(typeof(string), typeof(int), typeof(ITreeViewChoice));
private TreeModelSort _sortedModel;
private enum Column
{
Text,
SortValue,
Value
}
public JpgTreeView()
{
var valueColumn = new TreeViewColumn();
AppendColumn(valueColumn);
var visisbleColumnTextRenderer = new CellRendererText();
valueColumn.PackStart(visisbleColumnTextRenderer, true);
valueColumn.AddAttribute(visisbleColumnTextRenderer, "text", (int) Column.Text);
_sortedModel = new TreeModelSort(_store);
_sortedModel.SetSortColumnId((int) Column.SortValue, SortType.Descending);
_sortedModel.SetSortFunc((int) Column.SortValue, (model, a, b) =>
{
var aval = (int) model.GetValue(a, (int) Column.SortValue);
var bval = (int) model.GetValue(b, (int) Column.SortValue);
return aval.CompareTo(bval);
});
Model = _sortedModel;
}
The sorting works correctly but the _store.GetIterFirst function always retrieves the original first row (i.e. the first row of the unsorted list). _sortedModel.GetIterFirst returns the following error message:
gtk_list_store_get_value: assertion 'iter_is_valid (iter, list_store)' failed
I'm populating the TreeView using this function:
public void SetChoices(IEnumerable<ITreeViewChoice> choices)
{
_store.Clear();
foreach (var choice in choices)
{
_store.AppendValues(choice.GetChoiceText(), 1, choice);
}
}
How do I get the first row of the sorted list?
_sortedModel.GetIterFirst is the correct way to retrieve the first row of an ordered Gtk TreeView (TreeModelSort)
The issue I was having is that I was using the row returned from the TreeModelSort in the ListStore which isn't valid. i.e. this is correct:
_sortedModel.GetIterFirst(out var iter);
var value = _sortedModel.GetValue(iter, (int) Column.Value);
And this is incorrect (what i was doing):
_sortedModel.GetIterFirst(out var iter);
var value = _store.GetValue(iter, (int) Column.Value);
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.
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);
}