Sunday, July 13, 2008

Mono 101

I just got a nice email thanking me for covering "Mono 101", which I realized is a much better name for this blog.

Some of the things you are covering are "Mono 101" which I feel is lacking
badly. The more experienced programmers can always find a way to get things done
it's the beginners and intermediate that we need to address, help and give real
world examples to.


Thank You. I couldn't agree more, and I guess that was always a subconscience idea I had. I was already an experienced programmer when I started using Mono and I still had great trouble figuring out how to do so many basic things that just weren't covered. The best examples I found were few and far between and usually buried somewhere deep in the documentation. Anyone can figure out how to pack a VBox, but something as simple as stopping a window when it tries to close is damn near impossible to figure out.

So I have changed the name of the blog and now I actually have a conscience intention. To help fill in the gaps between the tutorials. Its just too bad I have to do it on this blog because getting any kind of access to update the (antiquated) Mono wiki is like breaking into Fort Knox. However if any of you official Mono people made it this far in the post without getting offended then I would love to offer my efforts (again, as I have in the past more than once) to contribute to the wiki, which would be far more helpful to more people than this blog.

Friday, July 11, 2008

Resize Pixbuf in TreeView/ComboBox

TreeView:


public ProjectsTreeView()
{
this.Model = projectModel;

this.AppendColumn("Icon", new CellRendererPixbuf(), new
TreeCellDataFunc(Render));
this.AppendColumn("Project", new
CellRendererText(), new TreeCellDataFunc(Render));
}

private void Render(TreeViewColumn column, CellRenderer cell, TreeModel
model, TreeIter iter)
{
switch (column.Title)
{
case
"Icon":

// Using the GetIcon and Scale is the easiest way to resize a pixbuf
Gdk.Pixbuf icon = ((Project)model.GetValue(iter,
0)).GetIcon.ScaleSimple(16, 16,
Gdk.InterpType.Bilinear);
((CellRendererPixbuf)cell).Pixbuf = icon;

break;
case "Project":
((CellRendererText)cell).Text =
((Project)model.GetValue(iter, 0)).Name; break;
}
}



ComboBox:


public class ProjectComboBox : ComboBox
{
public
ProjectComboBox()
{
this.Model = myProjectsModel;
CellRendererPixbuf
cIcon = new CellRendererPixbuf();
this.PackStart(cIcon,
true);
this.AddAttribute(cIcon, "pixbuf", 0);
CellRendererText cName = new
CellRendererText();
this.PackStart(cName, true);
this.AddAttribute(cName,
"text", 1);
this.SetCellDataFunc(cIcon,
RenderIcon);
this.SetCellDataFunc(cName, RenderName);
}
private void
RenderIcon(CellLayout layout, CellRenderer cellrenderer, TreeModel model,
TreeIter iter)
{

// Using the GetIcon and Scale is the easiest way to resize a pixbuf
((CellRendererPixbuf)cellrenderer).Pixbuf =
((Project)model.GetValue(iter, 0)).GetIcon.ScaleSimple(16, 16,
Gdk.InterpType.Bilinear);

}
private void RenderName(CellLayout layout,
CellRenderer cellrenderer, TreeModel model, TreeIter
iter)
{
((CellRendererText)cellrenderer).Text =
((Project)model.GetValue(iter, 0)).Name;
}
}

Wednesday, June 25, 2008

Draw a Rounded background border on a GdkWindow

Note that this does not draw the actual border of the control, just draws on the GdkWindow, like a psuedo-border

using System;
using Gtk;
using Gdk;
using Cairo;
using
System.Runtime.InteropServices;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;

[DllImport("libgdk-win32-2.0-0.dll")]
private static extern IntPtr gdk_cairo_create(IntPtr drawable);

private void DrawBorderRounded()
{
Context g = new
Context(gdk_cairo_create(myBG.GdkWindow));

double dWidth =
((Double)this.Allocation.Width) - 10.5;
double dWidthInner =
((Double)this.Allocation.Width - 15.5);
double dHeight =
((Double)this.Allocation.Height) - 10.5;
double dHeightInner =
((Double)this.Allocation.Height - 15.5);

PointD p1, p2, p3, p4, pA, pB,
pC, pD, pE, pF, pG, pH;
pA = new PointD(10.5, 15.5);
pB = new
PointD(15.5, 10.5);
pC = new PointD(dWidthInner, 10.5);
pD = new
PointD(dWidth, 15.5);
pE = new PointD(dWidth, dHeightInner);
pF = new
PointD(dWidthInner, dHeight);
pG = new PointD(15.5, dHeight);
pH = new
PointD(10.5, dHeightInner);
p1 = new PointD(10, 10);
p2 = new
PointD(dWidth, 10);
p3 = new PointD(dWidth, dHeight);
p4 = new
PointD(10, dHeight);

g.MoveTo(pA);
g.CurveTo(pA, p1, pB);
g.LineTo(pC);
g.CurveTo(pC, p2, pD);
g.LineTo(pE);
g.CurveTo(pE,
p3, pF);
g.LineTo(pG);
g.CurveTo(pG, p4, pH);
g.LineTo(pA);
g.ClosePath();

((IDisposable)g.Target).Dispose();
((IDisposable)g).Dispose();
}


Want to Fill the background with a gradient? Before the dispose add

// g.Save();
// Cairo.Gradient pat = new Cairo.LinearGradient(100, 200, 200,
100);
// pat.AddColorStop(0, new Cairo.Color(0, 0, 0));
//
pat.AddColorStop(1, new Cairo.Color(.240, .248, .3));
// g.Pattern = pat;
// Fill the path with pattern
// g.FillPreserve();
// We "undo" the
pattern setting here
// g.Restore();

NOTE: I feel like I saw this tutorial somehwere a long time ago. Probably on the Mono Wiki, however I don't feel like digging through all that nonsense to find out. So, if you are the proud owner of the original, or you recognize this code and know where I can find it, please tell me so I can give proper credit to the original. I'm just trying to pass on knowledge, not steal anybodies thunder

Enable TreeView RulesHint in Windows

I don't know why but the default windows mono install does not have TreeView RulesHint (alternating row colors) enabled by default. The fix is very easy though. Go to your Gtk-2.0 folder located in your default Mono install. Mine is located at:
C:\Program Files\Mono-1.9.1\etc\gtk-2.0
Open the file 'gtkrc' in a text editor like notepad or wordpad (it doesn't have an extension so windows won't recognize it, you'll have to use 'open with'
At the end of the file add these lines:

style "rules-hint"
{
GtkTreeView::allow-rules = 1
}

class "*" style "rules-hint"


Save and close the file and RulesHint should work

PostgreSql Membership Provider

I don't know about everyone out there but I have used just about every database available and I have settled on PostgreSql. Definitely my favorite. To use PostgreSql with the .Net MembershipProvider, a guy named Lee has put in some great work to make it happen. You can get it here: http://blog.woodchop.com/2006/09/postgresql-membership-provider-for.html

Stop a Window when it tries to close

// this = the window
this.DeleteEvent += delegate(object sender,
DeleteEventArgs e)
{
// Here I am checking to see if my Application is
on the login Screen
// If it is, then I can quit the program when this (my
main window) tries to close
if (IsOnLogin)
{
Application.Quit();
}
// If it is not on the login screen, I cancel the close and show the
user my login screen
else
{
NavigateTo("Login");
// This is the
only line you need to cancel a window close
e.RetVal = true;
}
};

Simple Drag-Drop between 2 TreeViews

This is the simplest way I can come with to drag drop between 2 TreeViews. I haven't tested it much but I'm sure it could be used with other widgets as well

TreeView treeView1 = new TreeView();
TreeView treeView2 = new TreeView();

TargetEntry[] te1 = new TargetEntry[] { new TargetEntry("treeView1 ",
TargetFlags.App, 1) };
TargetEntry[] te2 = new TargetEntry[] { new
TargetEntry("treeView2 ", TargetFlags.App, 2) };

treeView1
.EnableModelDragSource(Gdk.ModifierType.Button1Mask, te1, Gdk.DragAction.Move);
treeView1 .DragDataGet += treeView1 _DragDataGet;
treeView1
.EnableModelDragDest(te1, Gdk.DragAction.Move);
treeView1 .DragDataReceived
+= treeView1 _DragDataReceived;

treeView2
.EnableModelDragSource(Gdk.ModifierType.Button1Mask, te1, Gdk.DragAction.Move);
treeView2 .DragDataGet += treeView2 _DragDataGet;
treeView2
.EnableModelDragDest(te1, Gdk.DragAction.Move);
treeView2 .DragDataReceived
+= treeView2 _DragDataReceived;

//
// Drag and Drop from the First
TreeView to the Second
//
private void treeView1_DragDataGet(object o,
DragDataGetArgs args)
{
TreeModel model;
TreeIter row;
((TreeSelection)treeView1.Model.Selection).GetSelected(out model, out row);
args.SelectionData.Set(args.SelectionData.Target, 8,
Encoding.UTF8.GetBytes(model.GetValue(row, 0).ToString()));
}

private void treeView2 _DragDataReceived(object o, DragDataReceivedArgs
args)
{
// Get the string you passed through
string str =
Encoding.UTF8.GetString(args.SelectionData.Data)));

// Here you could do
a check to see whether or not the TreeView has the value already

// Add
to the second TreeView
treeView2.Model.AppendValues(str);

// Remove
from the first TreeView
treeView1.Model.Foreach(delegate(TreeModel mod,
TreePath path, TreeIter it)
{
if (mod.GetValue(it, 0).ToString() == str)
{
treeView1.Model.Remove(ref it);
return true;
}
return
false;
});
}

//
// Drag and Drop from the Second TreeView to
the First
//
private void treeView2_DragDataGet(object o,
DragDataGetArgs args)
{
TreeModel model;
TreeIter row;
((TreeSelection)treeView2.Model.Selection).GetSelected(out model, out row);
args.SelectionData.Set(args.SelectionData.Target, 8,
Encoding.UTF8.GetBytes(model.GetValue(row, 0).ToString()));
}

private void treeView1 _DragDataReceived(object o, DragDataReceivedArgs
args)
{
// Get the string you passed through
string str =
Encoding.UTF8.GetString(args.SelectionData.Data)));

// Here you could do
a check to see whether or not the TreeView has the value already

// Add
to the first TreeView
treeView1.Model.AppendValues(str);

// Remove
from the second TreeView
treeView2.Model.Foreach(delegate(TreeModel mod,
TreePath path, TreeIter it)
{
if (mod.GetValue(it, 0).ToString() == str)
{
treeView2.Model.Remove(ref it);
return true;
}
return
false;
});
}

Fun with the TreeView

// Single Click
myTreeView.Selection.Changed += new EventHandler(OnSelectionChanged);

// Double-Click
myTreeView.RowActivated += new RowActivatedHandler(myTreeView_RowActivated);

// Set the Selected Iter

// Declared outside the Constructor in the class somewhere so it can be seen in the entire class
TreeIter iterSelected;
private void OnSelectionChanged(object o, EventArgs args)
{
((TreeSelection)o).GetSelected(out iterSelected);
// Here you could also set what object is selected or do something with the selection
}

Converting Pixbuf to byte[] and back again

This simple code is useful for saving a Pixbuf to a database


// this.Icon is a byte[] that would eventually get saved to the db

public Gdk.Pixbuf GetIcon
{
get
{
// If there isn't an Icon return a
default
return (this.Icon == null) ? SomeDefaultPixbuf : new Pixbuf(this.Icon);
}
}
public void SetIcon(Gdk.Pixbuf
icon)
{

// Give it an image type
this.Icon = icon.SaveToBuffer("png");
}

Tuesday, June 24, 2008

The Mono Wiki - Or How I learned to stop worrying and learn to use Google

Before we get started: Yes, I am well aware that the Mono Project has a limited staff and they are all focused on other things, but to me this point seems very important... (Maybe I'm alone on this one)

The Mono Project is housed at Novell, a large company, and yet to me it has always seemed more like a hobby project. The lack of a respectable wiki and forums makes it seem as though Mono (no matter how great it is) is run out of a kids basement.
A few complaints:

The home page looks like a 1980's VCR with as much stuff packed onto it as possible, whether it is relevant or not. Is information about the Mono 2007 Summit really necessary?
On the home page, the links 'start' and 'contribute' take you to pages that seem to me to have more or less the same information, just split up. Related projects that give great first impressions would be
http://www.opensuse.org/ http://banshee-project.org/ http://www.gtk.org/

It seems like the forums are a compromise between the community who keeps asking for a great forums (based at the mono home site) and the developers who seem to love the mailing lists. It's not secret that http://www.asp.net/ and the msdn forums are heavily used for not just questions but also discussions, which end up being healthy for the community.

The actual 'wiki' part of the wiki is basically non-existent. Very rarely does new content go up and very rarely does obsolete content change. The only way for someone in the community to contribute is to post to the mailing list, which is like throwing a paper airplane into a black hole and hoping it ends up in the newspaper, and submitting an article on codeproject, which seems like a huge pain in the ass (hence this blog). I understand the developers are worried about incorrect or incomplete content being posted but that leaves Nobody contributing.

Maybe a solution would be to create 2 wiki's, one that is accessible to any registered user and another that is accessed only by administrators, and the admins could post good articles from the community wiki to the official wiki. Also, use usernames and tie the wiki into an official forums (real forums, not a list of emails from the mailing list).

These changes along with cleaning up the home page and the content of the wiki can give Mono a more professional look and attitude, which will go a long way towards generating interest and support. The appearance of the mono-project home web site has a strong effect on the project as a whole..

And I know you developers love the mailing lists, but they suck ass.

Some of my favorite Mono Resources

// If you haven't learned to use the Mono Documentation yet, stop what you're doing and do that now. Google is no match for the amount you can learn from perusing the Documentation correctly
http://www.go-mono.com/docs/index.aspx
http://www.mono-project.com/GtkSharp_TreeView_Tutorial
http://www.mono-project.com/GtkSharp:_Widget_Colours
http://mono-project.com/Responsive_Applications
http://www.go-mono.com/monologue/
http://www.mono-project.com/Mono.Addins
// Learn to read C/C++ and use the original Gtk Documentation as well
http://library.gnome.org/devel/gtk/stable/

NOTE: This post will continue to be updated as I find more great references

Rendering a ComboBox(Entry)

There is a good tutorial on the TreeView here. But when I went to try and execute something similar in a ComboBox I ran unto some problems.

Specifically what I was trying to do was render a List/TreeStore that held only specific objects instead of multiple columns, (new ListStore(typeof(Project))). This doesn't work the same way in a ComboBox but after much trial and error I came up with this:


public class ProjectComboBox : ComboBox
{
public ProjectComboBox() : base()
{
Initialize(null);
}

public ProjectComboBox(TreeModelFilter filter) : base()
{
Initialize(filter);
}

private void Initialize(TreeModelFilter filter)
{
// Set a filter if there is one
if (filter == null)
this.Model = MyCachedListStore;
else
this.Model = filter;

CellRendererPixbuf cIcon = new CellRendererPixbuf();
this.PackStart(cIcon, true);
this.AddAttribute(cIcon, "pixbuf", 0);

CellRendererText cName = new CellRendererText();
this.PackStart(cName, true);
this.AddAttribute(cName, "text", 1);

// This is how the Render mode is set
this.SetCellDataFunc(cIcon, RenderIcon);
this.SetCellDataFunc(cName, RenderName);
}

private void RenderIcon(CellLayout layout, CellRenderer cellrenderer, TreeModel model, TreeIter iter)
{
try
{
((CellRendererPixbuf)cellrenderer).Pixbuf = ((Project)model.GetValue(iter, 0)).GetIcon.ScaleSimple(16, 16, Gdk.InterpType.Bilinear);
}
catch
{
// HACK: Here we have the oddest 'Object Reference Not Set to an Instance of an Object' error ever
// It doesn't actually cause any problems that I can see so for now we're just going to catch it and ignore it
}
}

private void RenderName(CellLayout layout, CellRenderer cellrenderer, TreeModel model, TreeIter iter)
{
try
{
((CellRendererText)cellrenderer).Text = ((Project)model.GetValue(iter, 0)).Name;
}
catch
{
// HACK: Here we have the oddest 'Object Reference Not Set to an Instance of an Object' error ever
// It doesn't actually cause any problems that I can see so for now we're just going to catch it and ignore it
}
}
}

NOTE: If anybody can tell me why the Object Reference error is occurring and how to fix it, it would be much appreciated, but for now this solution works and doesn't have any problems so it's not that big a deal

Do I have to have everything in a single install folder?

In my projects I have multiple .dll's I reference with my project and when I go to the /bin folder I don't want to see all of them just hanging out in a jumble. Call me anal, but I like my code nice and organized. However if the referenced assemblies are not in the same folder as the executing assembly (and not in the GAC) then .Net throws a temper tantrum.

To solve this problem you can hook up to the AppDomain event AssemblyResolve and it will be called whenever .Net can't find your referenced assembly. You can then tell .Net where to find it.

The simplest code I could break it down to is:

public class Program
{
public static void Main()
{
// Tell .Net what to do if it can't find the referenced Assembly
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(currentDomain_AssemblyResolve);

// Program Initialization has to be in a different class (I dont know why)
MyProgram.Initialize();
}

// Your Assembly finding method
static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
// Set the Name of the .dll you are looking for (or .exe)
string assemblyName = (new AssemblyName(args.Name).Name) + ".dll";

// Tell .Net where to find your Assembly
// This can be anywhere you want
// In this case it is a subdirectory of the folder my executable is in
// Use Path.Combine instead of "/" or "\" for cross-platform reasons
string path = Path.Combine( Path.Combine( Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location.ToString()), "lib"), assemblyName);

// Load the Assembly
Assembly myAssembly = Assembly.LoadFrom(path);

// Optionally, if you have more than one directory you can check that
// here with something like:
// if(myAssembly == null)
// {
// path = Path.Combine( Path.Combine( Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location.ToString()), "lib2"), assemblyName);
// myAssembly = Assembly.LoadFrom(path);
// }

return myAssembly;
}
}

private class MyProgram
{
public static void Initialize()
{
Gtk.Application.Init();

new wMain();

Gtk.Application.Run();
}
}

Mono with Visual Studio

There seems to be a lot of questions about this and no real clear answers that I could find when I was beginning.

There is no real secret to using Mono and Gtk# with Visual Studio. You don't need the Mono compiler and you don't need to set any configuration settings.

Look under your Mono install directory, probably something like C:\Program Files\Mono-1.9.1\ and look in the Global Assembly Cache, which in my install is located at

C:\Program Files\Mono-1.9.1\lib\mono\gac

Look for these assemblies at least:

atk-sharp
gdk-sharp
glib-sharp
gtk-sharp

If you like you can also add glade-sharp, pango-sharp, mono.addins, mono.cairo, and any others you might need.

*Copy and paste those into your project /bin directory
*In Visual Studio right-click your project and select Add Reference, then browse to your bin directory and include those libraries
*Then build and run your project using Visual Studio. It should run just fine.

When you distribute your app just include those .dll's in the same folder as your executable and everything should work. In Linux you will need to add the .config files (like gtk-sharp.dll.config) to the bin as well. You should be able to find those in the mono install directory under your /usr directory (I think, I'm not using Linux at the moment so I'm not positive)

At any rate, that should do it. Very easy. Just Copy/Reference/Build.

Note: I am quite certain this is not the proper way to handle this and I should reference my global cache and set the vs project settings to use the mono compiler and blah blah blah, but seeing as how there are no clear instructions on how to easily do this, I have come up with this way, which I find simple and working (well). Shipping these libraries also assures my program is using the right one, without having to worry about which version of Mono is actually installed.

Wednesday, June 18, 2008

I Love Mono...

I just wanted to throw that out there from the beginning. Good job Mono team! Yall have obviously worked hard and it's much appreciated.

This blog is an attempt to give back to the community with code samples and tips and tricks that I learn along the way, that hopefully will help someone else.

I may also occasionally fall into a rant about something that bothers me about Mono, but be assured it is out of love and the hope of making a better product, and not a personal attack. After all, nothing is perfect and where would an open source community be without the opinion of the community...


Note: Some of the posts here may seem simplistic to Mono developers who know what they are doing, but they may be things I struggled with when I first began learning Mono, so maybe they'll help a few beginners>