Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I'm writing a textbox class for my XNA game and I'm using SpriteFont to draw the text. It's only a single line textbox so when the text width exceeds the size of the rectangle box, it currently just keeps going.

In Windows textboxes extra text is cut off so that you may only see half a letter. I'd like to emulate this with my custom control. However, I see no overload with SpriteBatch.DrawString that would allow for anything that could cut off text that didn't fit within certain bounds.

Does anyone know of a method that would allow for this? I'm still quite new to the XNA API so I'm not sure what out there exists for this sort of thing.. Thanks

share|improve this question
add comment

4 Answers 4

up vote 4 down vote accepted

The way I handled this in my simple textbox UI element was to use a scissor rectangle (as Nicolas Bolas said while I was writing this :P).

Here's the basic idea:

  1. First off, create a RasterizerState field in whatever class does the drawing of the text like so:

    RasterizerState _rasterizerState = new RasterizerState() { ScissorTestEnable = true };
    

    You'll pass this to the spritebatch to tell it to use the GraphicsDevice.ScissorRectangle.

  2. Then, your Draw method would go something like this:

    public void Draw(GameTime gameTime) {
        spriteBatch.Begin();
        //Draw other stuff
        spriteBatch.End();
    
        //Set up the spritebatch to draw using scissoring (for text cropping)
        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend,
                          null, null, _rasterizerState);
    
        //Optional: Draw the border using a 1x1 white square texture that is 
        //          stretched to fill a slightly larger rectangle behind the
        //          background (below).
        spriteBatch.Draw(_boxTexture, _borderRectangle, _borderColor);
    
        //Optional: Draw the background
        spriteBatch.Draw(_boxTexture, _textboxRectangle, _backColor);
    
        //Copy the current scissor rect so we can restore it after
        Rectangle currentRect = spriteBatch.GraphicsDevice.ScissorRectangle;
    
        //Set the current scissor rectangle
        spriteBatch.GraphicsDevice.ScissorRectangle = _textboxRectangle;
    
        //Draw the text at the top left of the scissor rectangle
        spriteBatch.DrawString(_font, _text, Vector2.Zero, _fontColor);
    
        //Reset scissor rectangle to the saved value
        spriteBatch.GraphicsDevice.ScissorRectangle = currentRect;
    
        //Draw more textboxes or whatever else using the scissor rectangles here
    
        //End the spritebatch
        spriteBatch.End();
    }
    

You can probably get away with not resetting the scissor rectangle for this basic situation, but you need to reset it if you want to draw other things (like those backgrounds). This example also assumed a lot of variables which you can infer (I hope :-) ) from their names.

A few other notes on the variables:

  1. _textboxRectangle is the clipping rectangle to use. I pulled this from my textbox UI class which is where that and basically all other variables comes from.
  2. _borderRectangle is just a rectangle that is a few pixels larger than _textboxRectangle so that when the background is drawn over top of it, it will appear as the border for the box. Pretty simple.

Hope this helps!

share|improve this answer
add comment

What you need to do is use SpriteFont.MeasureString()

Loop through your string and check MeasureString(substring) to see when it exceeds your maximum width. Then use that substring to render.

Edit: Sorry didn't realize you wanted that precision.

What you want to use is a ScissorRectangle.

  • Set GraphicsDevice.ScissorRectangle to the rect of your textbox.
  • Set ScissorTestEnable to true in the RasterizerState
  • Pass that RasterizerState to your SpriteBatch.Begin()
share|improve this answer
    
Yes that is what I am currently doing but the problem is it doesn't cut off text like the regular Windows textbox does. It gives no option towards rendering half a letter, for example, if the textbox size allows for it. –  John Price Feb 29 '12 at 3:37
add comment

If you want to clip things to a certain region, you use the scissor box. It's a property of the GraphicsDevice: GraphicsDevice.ScissorTestEnable and either RenderState.ScissorRectangle pre-XNA 4.0 or RasterizerState.ScissorTestEnable in XNA 4.0.

You don't need to measure text for it; just set the scissor box to the area you want to clip to. You can draw all the text you want after that. Indeed, scissoring is useful in all kinds of places for GUIs.

Just don't forget to turn off scissoring when you're done.

share|improve this answer
    
This actually looks like a great solution, I didn't know about this –  John McDonald Feb 29 '12 at 4:03
    
This looks promising! However, I'm not sure exactly where ScissorTestEnable is.. I'm using XNA 4.0. Do I need to create a RenderState? –  John Price Feb 29 '12 at 4:04
    
@JohnPrice: Sorry, I had some of the names and links wrong. They're corrected now. –  Nicol Bolas Feb 29 '12 at 4:17
    
Thanks, I got it working now. I'm a bit unsure on how to turn off scissoring though. Whenever I call rs.ScissorTestEnable = false; after my drawing I get an exception: Cannot change read-only RasterizerState State objects become read-only the first time they are bound to a GraphicsDevice. –  John Price Feb 29 '12 at 4:29
    
Then you'll need to keep two of them around. One with scissor test enable and one with it disabled. Switch the state bindings when you need to change them. –  Nicol Bolas Feb 29 '12 at 4:30
add comment

You need to use multiple render targets. You need to create an off-screen texture where you will draw the text-box area, then draw that to the main render target. Here's a post on the MSDN forum about this: http://social.msdn.microsoft.com/forums/en-US/xnaframework/thread/71263016-b26a-44bd-80f3-8cd620b119ff

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.