|
|
First off, thank you for sharing.
My only issue is a total lack of memory management. The control is creating lots of disposable objects, and none of them ever get disposed. Graphics are not disposed in the draw method, fonts are created left and right, none of which are properly disposed when done.
Also, instead of creating brushes like this:
new SolidBrush(SystemColors.WindowText)
It is more efficient to simply use the system brushes in the framework:
SystemBrushes.WindowText
Aside from these issues, this is a very nice control, and one I spent a while looking for. Once again, thank you.
|
|
|
|
|
|
Populate doesn't seem to catch when new fonts are added to the system. For example, add the control to the form, call .Popopulate(false). It will populate with installed fonts.
Download & install a new font to the system. Call .Populate(false) again while the program is still running, and you'll notice that new font isn't listed.
The only time populate works is when you restart the app completely; is there a way to get that to populate properly?
|
|
|
|
|
good question - any hints?
|
|
|
|
|
First of all big big thanks for the article, and the code you supplied.
It was my first time I truly understood the "OwnerDraw" hook, and I think it is the first time I used it correctly.
The changes I introduce are part performance, but also a lot of visual cleaning up and code cleanup that I felt was missed, also made the control a bit more generalized for usage from the Visual Form Designer.
List of changes:
- ComboBox and ToolStripComboBox "flavors:
- Sample text is user customized (also from Visual Designer)
- Font Size of sample is based on control font size
- Fonts are refreshed upon style changes
- all changes are synced, so are "more thread safe".
- Sample text (when used) is displayed on the far side of the list, giving a "cleaner" preview.
- Selected font, does not display Sample Text.
- hope I didn't miss anything....
I'd appreciate a thanks reply if someone finds it useful.
I would very much like a reply from the original poster.
Thanks, the code is here below:
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace FontCombo
{
public class FontComboBox : ComboBox
{
[DefaultValue("Sample Text")]
[Description("Sample Text to add following the font name")]
public string SampleText
{
get { return fontComboBoxHandler.SampleText; }
set { fontComboBoxHandler.SampleText = value; }
}
[DefaultValue(true)]
[Description("Display Font name in predefined control font, and only Sample Text in rendered font")]
public bool FontNameInNormalFont
{
get { return fontComboBoxHandler.FontNameInNormalFont; }
set { fontComboBoxHandler.FontNameInNormalFont = value; }
}
FontComboBoxHandler fontComboBoxHandler;
public FontComboBox()
{
fontComboBoxHandler = new FontComboBoxHandler(this);
}
}
public class ToolStripFontComboBox : ToolStripComboBox
{
[DefaultValue("Sample Text")]
[Description("Sample Text to add following the font name")]
public string SampleText
{
get { return fontComboBoxHandler.SampleText; }
set { fontComboBoxHandler.SampleText = value; }
}
[DefaultValue(true)]
[Description("Display Font name in predefined control font, and only Sample Text in rendered font")]
public bool FontNameInNormalFont
{
get { return fontComboBoxHandler.FontNameInNormalFont; }
set { fontComboBoxHandler.FontNameInNormalFont = value; }
}
FontComboBoxHandler fontComboBoxHandler;
public ToolStripFontComboBox()
{
fontComboBoxHandler = new FontComboBoxHandler(this.ComboBox);
}
}
public class FontComboBoxHandler
{
private int maxWidth;
bool fontNameInNormalFont = true;
public bool FontNameInNormalFont
{
get { return fontNameInNormalFont; }
set
{
if (value != fontNameInNormalFont)
{
maxWidth = 0;
fontNameInNormalFont = value;
}
}
}
private string sampleText;
public string SampleText
{
get { return sampleText; }
set
{
if (sampleText != value)
{
maxWidth = 0;
sampleText = value;
}
}
}
private readonly object syncLock = new object();
private Graphics graphics;
private ComboBox theCtrl;
public ComboBox TheCtrl { get { return this.theCtrl; } }
public FontComboBoxHandler( ComboBox ComboBoxToAttach )
{
this.theCtrl = ComboBoxToAttach;
this.graphics = theCtrl.CreateGraphics();
theCtrl.MaxDropDownItems = 20;
theCtrl.IntegralHeight = false;
theCtrl.Sorted = false;
theCtrl.DropDownStyle = ComboBoxStyle.DropDownList;
theCtrl.DrawMode = DrawMode.OwnerDrawVariable;
this.fontNameInNormalFont = false;
this.sampleText = "Sample Text";
theCtrl.FontChanged += new EventHandler(theCtrl_FontChanged);
theCtrl.DrawItem += new DrawItemEventHandler(theCtrl_DrawItem);
theCtrl.DropDown += new EventHandler(theCtrl_DropDown);
theCtrl.MeasureItem += new MeasureItemEventHandler(theCtrl_MeasureItem);
theCtrl.SizeChanged += new EventHandler(theCtrl_SizeChanged);
}
private void Populate()
{
lock (syncLock)
{
this.maxWidth = 0;
theCtrl.Items.Clear();
foreach (FontFamily ff in FontFamily.Families)
{
if (ff.IsStyleAvailable(FontStyle.Regular))
{
theCtrl.Items.Add(new Font(ff, theCtrl.Font.Size));
}
}
}
}
public void RefreshFonts()
{
lock (syncLock)
{
if (0 == theCtrl.Items.Count)
{
Populate();
}
else
{
for (int i = theCtrl.Items.Count - 1; i >= 0; --i)
{
Font f = (Font)theCtrl.Items[i];
theCtrl.Items[i] = new Font(f.FontFamily, theCtrl.Font.Size);
}
}
}
}
void theCtrl_SizeChanged(object sender, EventArgs e)
{
lock (syncLock)
{
if (0 == maxWidth && theCtrl.SelectedIndex > -1)
{
RefreshFonts();
}
}
}
void theCtrl_MeasureItem(object sender, MeasureItemEventArgs e)
{
lock (syncLock)
{
if (e.Index > -1)
{
int w = 0;
Font tmpFont = theCtrl.Items[e.Index] as Font;
string fontName = tmpFont.Name;
if (FontNameInNormalFont)
{
SizeF fontSize = graphics.MeasureString(SampleText, tmpFont);
SizeF captionSize = graphics.MeasureString(fontName, theCtrl.Font);
e.ItemHeight = (int)Math.Max(fontSize.Height, captionSize.Height);
w = (int)(fontSize.Width + captionSize.Width + graphics.MeasureString(" ", tmpFont).Width);
}
else
{
SizeF s = graphics.MeasureString(fontName, tmpFont);
e.ItemHeight = (int)s.Height;
w = (int)s.Width;
}
maxWidth = Math.Max(maxWidth, w);
e.ItemHeight = Math.Min(e.ItemHeight, theCtrl.Height);
e.ItemWidth = maxWidth;
}
}
}
void theCtrl_DropDown(object sender, EventArgs e)
{
lock (syncLock)
{
if (0 == maxWidth)
{
RefreshFonts();
}
theCtrl.DropDownWidth = maxWidth + 30;
}}
void theCtrl_DrawItem(object sender, DrawItemEventArgs e)
{
lock (syncLock)
{
if (e.Index > -1)
{
Font tmpFont = theCtrl.Items[e.Index] as Font;
string fontName = tmpFont.Name;
Brush backBrush = SystemBrushes.Window;
Brush textBrush = SystemBrushes.WindowText;
bool fontNameOnly = !FontNameInNormalFont || (DrawItemState.ComboBoxEdit == (e.State & DrawItemState.ComboBoxEdit));
Font fontNameFont = FontNameInNormalFont ? theCtrl.Font : tmpFont;
if (DrawItemState.Focus == (e.State & DrawItemState.Focus))
{
backBrush = SystemBrushes.Highlight;
textBrush = SystemBrushes.HighlightText;
}
if (fontNameInNormalFont)
{
}
e.Graphics.FillRectangle(backBrush, e.Bounds);
e.Graphics.DrawString(fontName, fontNameFont, textBrush, e.Bounds.Location);
if (!fontNameOnly)
{
e.Graphics.DrawString(SampleText, tmpFont, textBrush, e.Bounds.Right - 6 - (int)(e.Graphics.MeasureString(SampleText, tmpFont).Width), e.Bounds.Y);
}
}
}
}
void theCtrl_FontChanged(object sender, EventArgs e)
{
lock (syncLock)
{
maxWidth = 0;
}
}
}
}
Sincerely,
Lockszmith
modified on Thursday, August 11, 2011 9:26 AM
|
|
|
|
|
please can any body show me how to use fontcombo.dll in vb
|
|
|
|
|
Hello,
Its a very good functionality. Only I found small problem on HIGH DPI.
I changed the system DPI to 125. So the names are not coming properly.
Can you please look into that? Rest of the part is proper.
Thanks again for great coding...
|
|
|
|
|
Hi there,
I would also like to know if you can set the intial value of this to be "Arial" or "Times New Roman".
I've tried to put
this.fontComboBox1.SelectedIndex = "Arial"; (but of course it's looking for a number)
so I've tried
this.fontComboBox1.SelectedText = "Arial"; again looking for a number
No one's font lists are the same so numbers wouldn't work
Is there a way to set this to a specific font, initally.
Thanks,
YoonMi
|
|
|
|
|
Of course you can.
concept
First you have to manually iterate through items to find "Arial" index
then set the index in the populate method
use this method (not the best way but it works)
step 1
private int FindArial()
{
int index=1;
for (int x=0; x < this.Items.Count - 1; x++)
{
if (this.Items[x].ToString() == "Arial")
index = x;
}
return index;
}
step 2
then change the line SelectedIndex=0 in the public void Populate(bool b) method to
SelectedIndex=FindArial();
and it works
|
|
|
|
|
In your code:
for (int x=0; x < this.Items.Count - 1; x++)
Shouldn't that be
for (int x=0; x < this.Items.Count; x++)
Otherwise, you would miss out checking the last item in the combobox.
-LS
|
|
|
|
|
Larry_Smith_IPro wrote: In your code:
for (int x=0; x < this.Items.Count - 1; x++)
Shouldn't that be
for (int x=0; x < this.Items.Count; x++)
Otherwise, you would miss out checking the last item in the combobox.
i haven't checked this but i think you are right
|
|
|
|
|
When the items are indexed from zero then the items.count is NOT the last item. For example if you have five items, indexed as 0, 1, 2, 3 and 4 then you see that although the count is 5, the index of the last item is count-1 .
Life in the fast lane is only fun if you live in a country with no speed limits.
|
|
|
|
|
Is there any way to place this FontComboBox in a ToolStrip or ToolStripContainer?
I'm using Visual C# Express 2005, but I also have Visual C# Express 2008 loaded on my computer!
Awesome work by the way, just would like it to be placed in a ToolStrip if it's possible.
Thanks,
YoonMi
|
|
|
|
|
|
Hi
Man I want to make certain portion of the text appearing in text box control as Bold and other as Normal. Can we do this? Any idea?
Winners Never Quit And Quitters Never Win
-- modified at 4:36 Monday 16th January, 2006
|
|
|
|
|
Since a ComboBox AutoScales (ie, if you change the font size, the size of the control changes ),
you would probably want to make a change to your DrawItem method, and the MeasureItem method.
Instead of creating the default fon as a 10pt Arial, why not incorporate the Font property already
included in the ComboBox.
Try this:
Font font = new Font( this.Items[ e.Index ], this.Font.Size );
|
|
|
|
|
I added this to the FondComboBox class, it rescales the height of the box nicely. Trying to put the code in the paint handler caused flickering and stopped the paint.
protected override void OnSelectionChangeCommitted(EventArgs e)
{
int selection = this.SelectedIndex;
Font fChangedTo = new Font(this.Items[selection].ToString(), this.Font.Size);
this.Font = fChangedTo;
base.OnSelectionChangeCommitted(e);
}
|
|
|
|
|
I made changes to that calls to measure string are only made if the sring really needs to be created.
Make a member for the arial font used to display the name + example.
Made the size of the fonts use a constant.
Made the sample text constant.
Made it so that the both had a meaningful name to me since I could not tell without looking at the code.
Made it so it only created the font a single time as opposed to all the calls to create temporary fonts in the calls.
Changed the spacing for the sample after the font name to be next to the font. It was using X+Width*2 changed it to X*2+Width.
Oh I also removed the bitmap since in my usage it was just more noise that I did not really want.
Thanks for the example, it did pretty much everything I needed.
<br />
using System;<br />
using System.Collections;<br />
using System.ComponentModel;<br />
using System.Drawing;<br />
using System.Data;<br />
using System.Windows.Forms;<br />
<br />
namespace FontCombo<br />
{ <br />
public class FontComboBox : ComboBox<br />
{<br />
int _maxWidth = 0;<br />
bool _displayNameNormalFont = true;<br />
<br />
const string SAMPLE = " - Hello World";<br />
const int DEFAULT_SIZE = 10;<br />
<br />
Font _arial = new Font("Arial", DEFAULT_SIZE);<br />
<br />
public FontComboBox()<br />
{ <br />
MaxDropDownItems = 20;<br />
IntegralHeight = false;<br />
Sorted = false;<br />
DropDownStyle = ComboBoxStyle.DropDownList;<br />
DrawMode = DrawMode.OwnerDrawVariable; <br />
}<br />
<br />
public void Populate(bool displayNameNormalFont)<br />
{<br />
_displayNameNormalFont = displayNameNormalFont;<br />
<br />
foreach (FontFamily ff in FontFamily.Families)<br />
{<br />
if(ff.IsStyleAvailable(FontStyle.Regular))<br />
{<br />
Items.Add(ff.Name); <br />
}<br />
} <br />
<br />
if(Items.Count > 0)<br />
{<br />
SelectedIndex=0;<br />
}<br />
}<br />
<br />
protected override void OnMeasureItem(System.Windows.Forms.MeasureItemEventArgs e)<br />
{ <br />
if(e.Index > -1)<br />
{<br />
int w = 0;<br />
string fontName = Items[e.Index].ToString(); <br />
Font tmpFont = new Font(fontName, DEFAULT_SIZE);<br />
Graphics g = CreateGraphics();<br />
if( _displayNameNormalFont )<br />
{<br />
SizeF fontSize = g.MeasureString(SAMPLE, tmpFont);<br />
SizeF captionSize = g.MeasureString(fontName, _arial);<br />
e.ItemHeight = (int)Math.Max(fontSize.Height, captionSize.Width);<br />
w = (int)(fontSize.Width + captionSize.Width);<br />
}<br />
else<br />
{<br />
SizeF s = g.MeasureString(fontName, tmpFont);<br />
e.ItemHeight = (int)s.Height;<br />
w = (int)s.Width;<br />
}<br />
_maxWidth = Math.Max(_maxWidth, w);<br />
e.ItemHeight = Math.Min(e.ItemHeight, 20);<br />
}<br />
base.OnMeasureItem(e);<br />
}<br />
<br />
protected override void OnDrawItem(System.Windows.Forms.DrawItemEventArgs e)<br />
{ <br />
if(e.Index > -1)<br />
{<br />
string fontName = Items[e.Index].ToString(); <br />
Font tmpFont = new Font(fontName, DEFAULT_SIZE);<br />
<br />
if(_displayNameNormalFont)<br />
{<br />
Graphics g = CreateGraphics();<br />
int w = (int)g.MeasureString(fontName, _arial).Width;<br />
<br />
if((e.State & DrawItemState.Focus)==0)<br />
{<br />
e.Graphics.FillRectangle(new SolidBrush(SystemColors.Window), e.Bounds);<br />
e.Graphics.DrawString(fontName, _arial, new SolidBrush(SystemColors.WindowText), e.Bounds.X*2, e.Bounds.Y); <br />
e.Graphics.DrawString(SAMPLE,tmpFont,new SolidBrush(SystemColors.WindowText), e.Bounds.X*2+w, e.Bounds.Y); <br />
}<br />
else<br />
{<br />
e.Graphics.FillRectangle(new SolidBrush(SystemColors.Highlight), e.Bounds);<br />
e.Graphics.DrawString(fontName, _arial, new SolidBrush(SystemColors.HighlightText), e.Bounds.X*2, e.Bounds.Y); <br />
e.Graphics.DrawString(SAMPLE, tmpFont,new SolidBrush(SystemColors.HighlightText), e.Bounds.X*2+w, e.Bounds.Y); <br />
} <br />
}<br />
else<br />
{<br />
if((e.State & DrawItemState.Focus)==0)<br />
{<br />
e.Graphics.FillRectangle(new SolidBrush(SystemColors.Window), e.Bounds);<br />
e.Graphics.DrawString(fontName,tmpFont,new SolidBrush(SystemColors.WindowText), e.Bounds.X*2, e.Bounds.Y);<br />
}<br />
else<br />
{<br />
e.Graphics.FillRectangle(new SolidBrush(SystemColors.Highlight), e.Bounds);<br />
e.Graphics.DrawString(fontName,tmpFont,new SolidBrush(SystemColors.HighlightText), e.Bounds.X*2, e.Bounds.Y);<br />
} <br />
}<br />
}<br />
base.OnDrawItem(e);<br />
}<br />
<br />
protected override void OnDropDown(System.EventArgs e)<br />
{<br />
this.DropDownWidth = _maxWidth+30;<br />
} <br />
}<br />
}<br />
|
|
|
|
|
There is a problem in my owner draw combo. I download yours and found the same problem. It is:
Scroll down your combo by the mouse wheel(ofcourse after drop down), you will find the items in it looked scroll incorrect.
I trace it found that the message about the index seemed not suitable.
Is it a bug of the ComboBox?
Do you have some opinion on this?
And thanks very much for the article.
Echo
|
|
|
|
|
I am having the same problem. 
|
|
|
|
|
Hi,
I found this artical amazing . But I missed the posibility of typing a part of a fontname and go directly to it. So I made some modifications. What I still plan to do with this control (when I have some more time) is add a history in the beginning of the list. Like Word it will first repeat the 5 recently used and draw a double line under it.
1. Change the DropDownStyle to DropDown instead of DropDownList. This made allows owner drawed items with editable ComboBox.
<br />
public FontComboBox()<br />
{ <br />
MaxDropDownItems = 20;<br />
IntegralHeight = false;<br />
Sorted = false;<br />
DropDownStyle = ComboBoxStyle.DropDown;<br />
DrawMode = DrawMode.OwnerDrawVariable;<br />
<br />
}<br />
2. The above change gives a problem with the original code to draw the Highlight Items. The Combobox doesn't fire a Focus event anymore. So whe have to change the code with 2 separated if blocks.
<br />
protected override void OnDrawItem(System.Windows.Forms.DrawItemEventArgs e)<br />
{ <br />
if(e.Index > -1)<br />
{<br />
string fontstring = Items[e.Index].ToString();<br />
nfont = new Font(fontstring,10);<br />
Font afont = new Font("Arial",10);<br />
<br />
if(both)<br />
{<br />
Graphics g = CreateGraphics();<br />
int w = (int)g.MeasureString(fontstring, afont).Width;<br />
<br />
if((e.State & DrawItemState.Focus)==0)<br />
{<br />
e.Graphics.FillRectangle(new SolidBrush(SystemColors.Window),<br />
e.Bounds.X+ttimg.Width,e.Bounds.Y,e.Bounds.Width,e.Bounds.Height);<br />
e.Graphics.DrawString(fontstring,afont,new SolidBrush(SystemColors.WindowText),<br />
e.Bounds.X+ttimg.Width*2-10,e.Bounds.Y);
e.Graphics.DrawString(samplestr,nfont,new SolidBrush(SystemColors.WindowText),<br />
e.Bounds.X+w+ttimg.Width*2-20,e.Bounds.Y);
}<br />
if(e.State == DrawItemState.Selected)
{<br />
e.Graphics.FillRectangle(new SolidBrush(SystemColors.Highlight),<br />
e.Bounds.X+ttimg.Width+2,e.Bounds.Y,e.Bounds.Width,e.Bounds.Height);<br />
e.Graphics.DrawString(fontstring,afont,new SolidBrush(SystemColors.HighlightText),<br />
e.Bounds.X+ttimg.Width*2-10,e.Bounds.Y);
e.Graphics.DrawString(samplestr,nfont,new SolidBrush(SystemColors.HighlightText),<br />
e.Bounds.X+w+ttimg.Width*2-20,e.Bounds.Y);
} <br />
}<br />
else<br />
{<br />
if((e.State & DrawItemState.Focus)==0)<br />
{<br />
e.Graphics.FillRectangle(new SolidBrush(SystemColors.Window), e.Bounds.X+ttimg.Width,e.Bounds.Y,e.Bounds.Width,e.Bounds.Height);<br />
e.Graphics.DrawString(fontstring,nfont,new SolidBrush(SystemColors.WindowText),<br />
e.Bounds.X+ttimg.Width*2-10,e.Bounds.Y);
}<br />
if(e.State == DrawItemState.Selected)
{<br />
e.Graphics.FillRectangle(new SolidBrush(SystemColors.Highlight),<br />
e.Bounds.X+ttimg.Width,e.Bounds.Y,e.Bounds.Width,e.Bounds.Height);<br />
e.Graphics.DrawString(fontstring,nfont,new SolidBrush(SystemColors.HighlightText),<br />
e.Bounds.X+ttimg.Width*2-10,e.Bounds.Y);
} <br />
}<br />
<br />
e.Graphics.DrawImage(ttimg, new Point(e.Bounds.X, e.Bounds.Y)); <br />
}<br />
base.OnDrawItem(e);<br />
}<br />
3. Fianally the DropDownBox becomes somtimes to large. When we see how the FontCombo of Word handles the, we see it cuts the rightpart of the preview. Then we can change to next code.
<br />
protected override void OnDropDown(System.EventArgs e)<br />
{<br />
this.DropDownWidth = maxwid+30;<br />
if(this.DropDownWidth > 270)
{<br />
this.DropDownWidth = 270;<br />
}<br />
}<br />
Just to demonstrate how powerfull C# is and how you can mod this code .
Greetz
Xavier
|
|
|
|
|
Can you point me to a good VB source on something like this?...
Thanks...
|
|
|
|
|
... soes a fire krekker Nish. (bit of local SA flavour there.)
Definitley love the way you show both what the font looks like and it's name in a default, readable font. Word should do that without a doubt
Also a good display of taking Senkwes article and expanding upon it, showing what can be down with ownerdrawn methods.
regards,
Paul Watson
Bluegrass
Cape Town, South Africa
|
|
|
|
|
I could never understand why it tooks so long for applications like Adobe Photoshop and MS Word to add this functionality, and now you added it for VS.NET, great work here, very useful tool.
Soliant | email
"The whole of science is nothing more than a refinement of everyday thinking." -Albert E.
|
|
|
|
|