Solved

CRichEditView text formatting

Posted on 1998-04-09
14
597 Views
Last Modified: 2013-11-20
ok, I'm trying to change the format of the text in a CRichEditView...nothing fancy, I'd just like to set the tabs to a width of 4 characters and to change the font to a user specified font, which is represented as the CFont variable "currfont."  Here's what I have so far:

CRichEditCtrl &ctrl = GetRichEditCtrl();

CHARFORMAT cf;
memset(&cf, 0, sizeof(cf));
LOGFONT lf;
memset(&lf, 0, sizeof(lf));

currfont->GetLogFont(&lf);

cf.cbSize = sizeof(cf);
cf.dwMask = CFM_FACE | CFM_ITALIC | CFM_SIZE | CFM_STRIKEOUT | CFM_UNDERLINE | CFM_COLOR | CFM_BOLD;
if (lf.lfItalic)
      cf.dwEffects |= CFE_ITALIC;
if (lf.lfStrikeOut)
      cf.dwEffects |= CFE_STRIKEOUT;
if (lf.lfUnderline)
      cf.dwEffects |= CFE_UNDERLINE;
cf.dwEffects |= CFE_AUTOCOLOR;
cf.yHeight = lf.lfHeight;
cf.bCharSet = lf.lfCharSet;
cf.bPitchAndFamily = lf.lfPitchAndFamily;
strcpy(cf.szFaceName,lf.lfFaceName);
ctrl.SetDefaultCharFormat(cf);

PARAFORMAT pf;
int i;
pf.cbSize = sizeof(pf);
pf.dwMask = PFM_TABSTOPS;
pf.cTabCount = 20;
for (i = 0; i < 20; i++)
     pf.rgxTabs[i] = i * 4;
ctrl.SetParaFormat(pf);

Obviously, it doesn't bold the text, even with "bold" selected (although it would be nice if it would), but most perplexing is that it won't change the text size and the tabs are completely screwed up.  Any idea how to change this so it works?

                         -W.W.
P.S. The text should not be able to change color...i.e. it should be plain black.
0
Comment
Question by:openGL
  • 6
  • 4
  • 4
14 Comments
 
LVL 11

Expert Comment

by:mikeblas
Comment Utility
You're not setting the tab stops correctly--you don't understand how tab stops in the control work.

The control lets you specify tab stop positions in twips, not in character columns. The control may display proportional width fonts, so it's impossible to know that a tab stop happens every nth character unless you also know that you're using a monospace font.

If you're using a monospace font and want to have the tabstops at every 4th character, you'll need to do some conversions to get the right values for the tabstops array. Your code above already gets the LOGFONT for your font, and that has an lfWidth member which shows the average width of a character in the font in pixels on the device where the font's selected.

Assuming your font is selected into a display device, you can get the ratio between horizontal pixels and inches by asking the GetDeviceCaps() function for LOGPIXELSX.  So, that might look like:

   HDC hdc = ::GetDC();
   int nPixels = ::GetDeviceCaps(hdc, LOGPIXELSX);

Then, since you now know that there are nPixels for each inch on your display, and you know that there are 1440 twips per inch, you can get the number of twips for the width of the characters in your font with this expression:

   int nWidth = (logfont.lfWidth * nPixels) / 1440;

Then, you'd use four-times that value in your loop:

   PARAFORMAT pf;
   int i;
   pf.cbSize = sizeof(pf);
   pf.dwMask = PFM_TABSTOPS;
   pf.cTabCount = 20;
   for (i = 0; i < 20; i++)
        pf.rgxTabs[i] = nWidth * i * 4;
   ctrl.SetParaFormat(pf);

and that's that.

.B ekiM  



.B ekiM

0
 

Author Comment

by:openGL
Comment Utility
OK, couple of problems here, the first and foremost being that the above code doesn't work, mostly because the lfWidth of the LOGFONT struct is always 0.  The second problem is that you didn't even address how to change the size of the font...lfHeight is always a negative number which has little to do with the font size...help me out here.

        -W.W.
0
 
LVL 11

Expert Comment

by:mikeblas
Comment Utility
lfWidth will be zero if the font hasn't been selected into a device context.  You've got to select it someplace before it has metrics.

.B ekiM
0
 

Author Comment

by:openGL
Comment Utility
what are you talking about? a logical font can have an average width without being selected into a dc, and anyway lets say I have the following:
      dc.SelectObject(currfont);
      CRichEditCtrl &ctrl = GetRichEditCtrl();
      CHARFORMAT cf;
      memset(&cf, 0, sizeof(cf));
      LOGFONT lf;
      memset(&lf, 0, sizeof(lf));
      SetFont(currfont);
      currfont->GetLogFont(&lf);
      PARAFORMAT pf;
      int i;
      CClientDC dc(this);
      int Pixels = dc.GetDeviceCaps(LOGPIXELSX);
      int Width = (lf.lfWidth * Pixels) / 1440;
      pf.cbSize = sizeof(pf);
      pf.dwMask = PFM_TABSTOPS;
      pf.cTabCount = 20;
      for (i = 0; i < 20; i++)
            pf.rgxTabs[i] = Width * i * 4;
      ctrl.SetParaFormat(pf);
Wouldn't the call to SetFont select the font into the dc anyway? In any case, why is GetLogFont() not returning the correct values for the font?
0
 
LVL 10

Accepted Solution

by:
RONSLOW earned 100 total points
Comment Utility
Font height can be negative - it means you are specifying char height instead of cell height.  it can bne zero as well, which means use a sensible default.

Font width can be zero - it means to calculate the width from the height based on aspect ratios etc

So GetLogFont may well be returning the correct values anyway.

SetFont won't select it into the DC - it just sets what the default font will be for that window when a dc is created.  It is SelectObject that selects a font into a DC

To get the average char width, why not just use GetTextMetric after selecting the font into the DC with SelectObject ?

PS: in general code like
  CHARFORMAT cf;
  memset(&cf, 0, sizeof(cf));
is not nice (and sometimes unsafe) in C++ (when classes may have vtables and references etc) .. maybe you could just try
  CHARFORMAT cf = {0};

0
 

Author Comment

by:openGL
Comment Utility
Seeing as how CHARFORMAT is a struct and not a class, I don't see anything wrong with the above code.  In any case, Using GetTextMetric returns a number from 9-11 as the average width...now what? The equation mentioned earlier (e.g. tabwidth = LOGPIXELSX * fontwidth / 1440) doesn't work since the resulting answer always comes out to be less than one.
0
 
LVL 10

Expert Comment

by:RONSLOW
Comment Utility
You've gotten it backwards...

GetTextMetrics returns the height in logical units.
GetDeviceCaps(LOGPIXELSX) return the logical units per inch
1440 is the number of tips per inch (a twip is 1/1440th")

You need to convert the logical units to twips .

So, you need to divide logical units by logpixelsx to give number of inches and multiply by 1440 to give twips ie.

logpixelsx = pDC->GetDeviceCaps(LOGPIXELSX);
twipwidth = ::MulDiv(tmAveCharWidth,1440,LOGPIXELSX);

BTW: if you ever do need to do calcs like that, use MulDiv rather than doing a multiply and divide to avoid integer trunctation errors.

0
Free Trending Threat Insights Every Day

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

 
LVL 11

Expert Comment

by:mikeblas
Comment Utility
CHARFORMAT is a struct, not a class. Worrying about a memset() call on it is a waste of breath.

.B ekiM

0
 
LVL 10

Expert Comment

by:RONSLOW
Comment Utility
I was saying in general using memset on a class or struct (they are equivalent anyway apart from default member access) is bad news and should be avoided.

If it is your own class/struct, then you should provide a constructor that initializes the members.

If it is someone elses, then you shouldn't assume that it is safe to do a memset - in case the other person changes the implementation.

Yes, in this PARTICULAR case it happens to be OK, but it is just bad programming practice.

I don't believe good programming practice is a "waste of breath".

0
 
LVL 11

Expert Comment

by:mikeblas
Comment Utility
It's not bad programming practice as there's nothing wrong with calling memset() on a struct. struct's aren't equivalent to classes, especially the way you're measuring them here: a struct can't have virtual functions.

Even when a class does have virtual functions, using memset() on a pointer to the class for sizeof(theclass) sets the member data, not the vtable.

Good programming practice isn't a waste of breath, but dubious advice certainly is.

.B ekiM

0
 
LVL 10

Expert Comment

by:RONSLOW
Comment Utility
Wrong on several counts there, Mike.

* structs ARE equivalent to classes - the ONLY difference is that the members are public rather than private by default.

* A struct CAN have virtual functions (and constructors etc) and can dervie from other structs etc

* sizeof DOES include the vtable pointer.

* Doing a memset on a struct with virtual functions kills the vtable pointer

This PARTICULAR struct is a simple one with no constructors or virtual functions etc, so the memset is safe.  BUT in GENERAL it is STILL not good practice.

Please get your facts straight, Mike, before you criticise my advice as being 'dubious', especially when yours is just plain wrong !!


PS: Here is some code that demonstrates this

#include <stdio.h>
#include <stdlib.h>
struct S {
  int x;
  S() : x(1) {}
  virtual int f() { return x; }
};
struct T {
  int x;
};
int main (void) {
  S s;  T t;  S* ps = &s;
  printf ("%u\n",sizeof(s));
  printf ("%u\n",sizeof(t));
  int i; unsigned char* p;
  p = reinterpret_cast<unsigned char*>(&s);
  printf("%p %p :",p,&s);
  for (i = 0; i < sizeof(s); i++) {
    int byte = *p++;
    printf (" %2x",byte);
  }
  printf("\n");
  printf("%p %p :",p,&t);
  p = reinterpret_cast<unsigned char*>(&t);
  for (i = 0; i < sizeof(t); i++) {
    int byte = *p++;
    printf (" %2x",byte);
  }
  printf("\n");
  printf ("calling ps->f() gives %d\n",ps->f());
  memset(&s,0,sizeof(s));
  p = reinterpret_cast<unsigned char*>(&s);
  printf("%p %p :",p,&s);
  for (i = 0; i < sizeof(s); i++) {
    int byte = *p++;
    printf (" %2x",byte);
  }
  printf("\n");
  printf("%p %p :",p,&t);
  memset(&t,0,sizeof(t));
  p = reinterpret_cast<unsigned char*>(&t);
  for (i = 0; i < sizeof(t); i++) {
    int byte = *p++;
    printf (" %2x",byte);
  }
  printf("\n");
  printf ("calling ps->f() gives %d\n",ps->f());
  return 0;
}

Here is the (anotated) output from it

8    // sizeof S includes the vtable
4    // sizeof T does not
0064FDEC 0064FDEC : 54 f0 40  0  1  0  0  0    // see the vtable and initial value
0064FDF4 0064FDE8 : 3f  1  0  0    // rubbish because no constructor
calling ps->f() gives 1    // virtual function call works
0064FDEC 0064FDEC :  0  0  0  0  0  0  0  0    // no vtable any more
0064FDF4 0064FDE8 :  0  0  0  0    // zeroed out
and then it crashes due to the vtable pointer being destroyed.

0
 

Author Comment

by:openGL
Comment Utility
Hey people! The question was answered! The app works great! My god, stop arguing for pete's sake!! :)
0
 
LVL 10

Expert Comment

by:RONSLOW
Comment Utility
I am smply answering the invalid criticisms Mike made of my advice to (in general) not use memset of classes and structs

Unless you are sure that the class/struct does NOT have virtual functions or constructors, and if it contains or derives from other classes/structs that these also satisfy this requirement.  (ie. it should be an "aggregate").

In such a case then it is safe to use a memset, BUT it is safer to use an initializer when defining the struct variable.  An initializer will not compile if the struct is NOT an aggregate (protecting yourself from errors), whereas memset will clobber anything it is given).

I know that the answer has been given and these points are not directly relevant to the question.  However, this thread will remain in the PAQ and I would not like to see someone read this and go away believing that memset's are always safe with structs and cannot clobber vtable pointers in classes when that is simply NOT the case.

.O regoR

:-)

0
 
LVL 10

Expert Comment

by:RONSLOW
Comment Utility
Mike, perhaps you were getting confused between structs and unions?

Unions cannot be derived from, nor can they dervie from a class or struct).  Unions cannot have virtual functions (although they can still have member functions including constructors and destructors - unless anonymous).

0

Featured Post

Enabling OSINT in Activity Based Intelligence

Activity based intelligence (ABI) requires access to all available sources of data. Recorded Future allows analysts to observe structured data on the open, deep, and dark web.

Join & Write a Comment

Introduction: Ownerdraw of the grid button.  A singleton class implentation and usage. Continuing from the fifth article about sudoku.   Open the project in visual studio. Go to the class view – CGridButton should be visible as a class.  R…
Have you tried to learn about Unicode, UTF-8, and multibyte text encoding and all the articles are just too "academic" or too technical? This article aims to make the whole topic easy for just about anyone to understand.
This video will show you how to get GIT to work in Eclipse.   It will walk you through how to install the EGit plugin in eclipse and how to checkout an existing repository.
This video demonstrates how to create an example email signature rule for a department in a company using CodeTwo Exchange Rules. The signature will be inserted beneath users' latest emails in conversations and will be displayed in users' Sent Items…

744 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

16 Experts available now in Live!

Get 1:1 Help Now