2

I have a custom TextBox which overrides the OnRender method. Unfortunately, OnRender does not work properly when I add more than 143-145 TextBoxes to the grid.

This is what a windows with 160 TextBoxes looks like in the wpf designer. Each TextBox sets the border brush to red in the OnRender Method. For the last column of textboxes, OnRender does not work anymore.

render test example http://s3.postimage.org/id6jvq09n/render_Test_Example.png

The problem is not bound to the wpf designer, the same happens at runtime. Funnilly enough, if you delete one component inside the designer or at runtime once it has been rendered, then all the other controls suddenly work.

example code:

MytextBox.cs
RenderTestPanel.xaml
RenderTestPanel.xaml.cs

Timwi
  • 65,159
  • 33
  • 165
  • 230
Markus
  • 2,174
  • 2
  • 22
  • 37
  • 2
    Do you really need to display that many TextBoxes? Try using TextBlocks, and display a TextBox only when the user chooses to "edit" a cell. TextBlocks are much lighter weight. – Tim Lloyd Sep 30 '11 at 13:12
  • In addition to Chibacity's comment, why don't you just use a Style to set the `BorderBrush` to Red? Or even set the brush to Red in the Constructor, where it will be set once, instead of `OnRender` which will get called many times? – Rachel Sep 30 '11 at 13:25
  • 1
    @chibacity Yes, we do have to display that many TextBoxes. We're converting maps from an old system to c# panels and some of these old maps contain grids of, for example, 10x15 modifieable textfields. They're like tables made of textfields. – Markus Sep 30 '11 at 13:26
  • 1
    @Markus You could display them as TextBlocks, and only display a TextBox when the user clicks a cell. The user can only edit one cell at a time, so in theory they do not need to be TextBoxes all at the same time. – Tim Lloyd Sep 30 '11 at 13:33
  • @Rachel There are far more properties which are set in OnRender. BorderBrush is set in OnRender because it was the most convenient way to get our border behaviour done. For example, in wpf designer, the user may change the display mode from "DESIGN" to "RUNTIME". In Design mode, the textfield renders it's Border either red or green, depending on its type. In runtime mode, read only textfields are rendered without borders and modifieable textfields are rendered with border. We're also drawing underlines in OnRender because TextDocoration does not work the way we need it. and so on.. – Markus Sep 30 '11 at 13:34
  • @MarkusSchütz The items you have mentioned so far can also be easily and more efficiently accomplished with a Style and some Triggers. For example, put a break point in your `OnRender` method and see how often it gets called - It's a lot. Items like that are better set once, not every time the control renders. I would also still recommend what chibacity suggests. – Rachel Sep 30 '11 at 13:40
  • @chibacity I'd like to avoid hacks like this. The textboxes have undergone a lot of modifications to their appearance and behaviour and hacks like this might break their behaviour and introduce bugs. – Markus Sep 30 '11 at 13:43
  • @Rachel OnRender is called twice. And one odd thing I've noticed is, that the first time OnRender is called, it's called for every textbox and the second time only for the 144 textboxes which are rendered correctly. After that, OnRender was only called again when I made changes to the layout. Like removing a component, which is the reason that after removing a component, all fields were displayed correctly. Entering text and resizing the window did not cause a call to OnRender. – Markus Sep 30 '11 at 13:46
  • @Rachel By the way, how can I draw underlines with a style? The underline has to fill the whole textbox, even if it does not contain any text. – Markus Sep 30 '11 at 13:48
  • @Markus No offence here, but what I am suggesting is not a hack. Drawing text, and then substituting an editable control is an incredibly common way of doing things in GUI applications in many frameworks across many operating systems. :) – Tim Lloyd Sep 30 '11 at 13:50
  • @chibacity Seriously? Never seen anyone doing this, but I might as well try it if I can't get that much TextBoxes to work. – Markus Sep 30 '11 at 13:53
  • @MarkusSchütz Hrrm I must be thinking of another method then. I still think from what you described that a Style with some Triggers is a better way to go. Also, the reason for displaying a large number of TextBoxes as TextBlocks is because TextBox usually renders as 11 different components, while a TextBlock is 1. When you multiply 11 * 144, you get over 1500 components being drawn instead of just 144. I know that WPF applications do encounter performance issues if you're drawing too many components, and this is one way of reducing the number of controls in the application. – Rachel Sep 30 '11 at 13:54
  • @Markus Yes, seriously, a lot of applications draw text, and then substitute\overlay editable controls as needed. Do you think Excel uses millions of text boxes for instance. It uses text rendering, editable control substitution, and control virtualization to give the impression of having millions of editable cells. You can only ever edit a single cell at a time - this is key. – Tim Lloyd Sep 30 '11 at 13:55
  • @MarkusSchütz As for drawing the underline that stretches the entire width of the control, I usually use the control's Border (or add my own if it doesn't exist), and set the `BorderThickness="0,0,0,1"` – Rachel Sep 30 '11 at 13:56
  • Thanks for your suggestions, I'll try them out in 2 weeks. Not at work next week. – Markus Sep 30 '11 at 14:01
  • @chibacity Using TextBlock instead of TextBox does not help. – Markus Oct 10 '11 at 09:56
  • @Markus That surprises me. WPF should have no issue drawing 144 TextBlocks. – Tim Lloyd Oct 10 '11 at 12:05
  • @chibacity 144 TextBlocks are not the problem, 144 Components with OnRender are. I renamed OnRender() to UpdateAppearance() and I call it whenever a property that affects appearance is changed. Essentialy, this means the old OnRender() Method is called even more often but there is no noticable slowdown. It works, except for the underline which needed the RenderContext. But I'm sure I'll find a workaround for that. – Markus Oct 10 '11 at 17:29
  • In case someone wonders: I do the whole updateApperance for every property because 1 appearance attribute(such as border or background) might depend on multiple textbox properties and it's much easier and better readable if the whole appearance update is done in one function instead of each properties setter. – Markus Oct 10 '11 at 17:36

2 Answers2

0

I was able to work around a very similar problem to this. I posted the resolution here: https://stackoverflow.com/a/40605635/5823234

Community
  • 1
  • 1
Mo Jiwa
  • 33
  • 5
0

Your approach should be the one suggested by chibacity. This type of behavior is standard and is even used by the DataGridTextColumn that ships with WPF. From the MSDN:

DataGridTextColumn creates a TextBlock element in the non-editing mode and a TextBox element in the editing mode.

Also, as suggested by many other users in comments, you should not override OnRender to adjust the visual appearance of a control. In WPF, changes to a control's visual appearance can be accomplished by adjusting the control's Style and/or Template. The following style results in the exact same appearance change as your OnRender override:

<Style TargetType="TextBox">
  <Setter Property="BorderBrush" Value="Red" />
</Style>

You should only "derive and override" when you're extending the functionality and/or purpose of a control and there's nothing in your example that suggests that's what you're doing.

Additionally, your RenderTestPanel.xaml implies that all you're doing is creating a data grid which is provided by WPF. I would strongly suggest using the out-of-the-box DataGrid, styling the DataGridTextColumn and you'll (probably) accomplish your goals with less code and entirely in XAML.

sellmeadog
  • 7,437
  • 1
  • 31
  • 45
  • I can't use DataGrid. I'm converting existing console applications to c#. These applications did not have datagrids/tables so instead, developers used textfield arrays to create tables. I have to use textboxes/textfields. As explained in the comments above, I've set the border color inside OnRender because the border is not always red. Inside the wpf designer, the user can switch between Designmode and Runtime Mode. In design mode, readonly fields have red borders and modifieable fields have green borders. – Markus Oct 01 '11 at 13:04
  • In runtime mode, readonly fields have no border and modifieable fields have a slightly visible border. This might be possible without OnRender but there is more than just the border. How can I draw Underlines that fill the whole textfield without OnRender? Can this be done with styles? And I also have other attributes that look different in design and in runtime view. How to do this with styles? Rachel suggested triggers but thats something I'll have to look up once I get back to work. – Markus Oct 01 '11 at 13:06