Selecting Text inside a Multi-Inline TextBlock - c#

I have some textblocks composed of Runs. I want the characters within the textblock to be individually selectable like one would expect.
Example XAML:
<TextBlock>
<Run Text="Cant-individually-select" />
<Run Text="Still-Cant-individually-select" />
</TextBlock>
Is there any way to make the run inside the textblock behave like a standalone run? I see there a lot of solutions involving styling a TextBox to look like a TextBlock, but this isn't possible when I need multiple runs.

Related

How to bind two elements to the same property in WPF - C#

I'm using C# in a WPF application with MVVM (with Caliburn Micro framework). I'm trying to bind 2 elements (one TextBlock and one TextBox) to the same property, that resides in my model view. My property is called FirstName.
I have two options to do the binding: Binding Path=FirstName or x:Name=FirstName. When I edit the textbox, I see the changes in the textblock only if I bind in a certain way (see code). Any idea of why the other way does not work? (when I type in the textbox I don't see my textblock updates)
I've tried different mode options (two ways, one way, etc). The NotifyOfPropertyChange seems to be working.
<!-- This works -->
<TextBlock Text="{Binding Path=FirstName}"/>
<TextBox x:Name="FirstName"/>
<!-- This does not work -->
<TextBlock x:Name="FirstName"/>
<TextBox Text="{Binding Path=FirstName, Mode=TwoWay}"/>
With your second example, you need to specify UpdateSourceTrigger=PropertyChanged:
<TextBlock x:Name="FirstName"/>
<TextBox Text="{Binding Path=FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
Otherwise, the source is only updated when the TextBox loses focus.

Is it possible to bind to a property on the container of the Adorned Element?

In my WPF app we are using an adorner for displaying validation messages, in the particular case there is a single row grid that has multiple controls some of which have validation. The problem I'm having is that I want to force the width of the error message control to be the same as the grid but can't seem to find a way to reference that grid from the adorner template. Here is a sample of what I tried:
<ControlTemplate x:Key="Local_TopAdornedTemplateWide">
<StackPanel>
<AdornedElementPlaceholder x:Name="adornedElement"/>
<TextBlock MaxWidth="{Binding Path=ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType=Grid}, ElementName=adornedElement}"
TextWrapping="Wrap"
Text="{Binding Converter={StaticResource Local_ValidationErrorMessageConverter}}"
Style="{DynamicResource Error_Text}"
Padding="2 1 0 0"
Visibility="{Binding ElementName=adornedElement, Mode=OneWay, Path=AdornedElement.IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
</StackPanel>
</ControlTemplate>
This causes the application to crash with an XamlParseException.
Ideally the solution would not be specific to a grid so that it would get the width of any container type, but for now grid is the only use case.
Edit:
Here is an example of another template we use in the application; this template would not work for my case as it would limit the error to be the width of a single column of the aforementioned grid:
<ControlTemplate x:Key="Local_TopAdornedErrorTemplate">
<StackPanel>
<AdornedElementPlaceholder x:Name="adornedElement"/>
<TextBlock MaxWidth="{Binding ElementName=adornedElement, Path=ActualWidth}"
TextWrapping="Wrap"
Text="{Binding Converter={StaticResource Local_ValidationErrorMessageConverter}}"
Style="{DynamicResource Error_Text}"
Padding="2 1 0 0"
Visibility="{Binding ElementName=adornedElement, Mode=OneWay, Path=AdornedElement.IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
</StackPanel>
</ControlTemplate>
Using snoop I captured the following two screenshots (I could not take one of the full stack to prevent posting anything proprietary)
This shot shows the grid I mentioned previously, within this it is the FinancialTextBox item that is being adorned
This shot shows two things, the item selected in blue is the highest ancestor of the grid in the previous shot, the yellow highlight is the Textbox from the content template
With those two it seems to be apparent that (based on information from Contango's answer) the two items aren't not in the same visual tree which would lead me to believe my question is not possible. However the second template I added (which does work) points that at least some visual information from the adorned element lives on in the place holder.
So now my question boils down to a) does this information include the parent of the adorned element and b) how can this be accessed via a binding on a different element?
This ended up being a lot simpler than the path I was trying to go down.
I was doing some reading on the AdornedElementPlaceholder class and came across this entry on MSDN and noticed that the class actually has a property called parent, with that I tried the following binding and it works perfectly:
MaxWidth="{Binding ElementName=adornedElement,
Mode=OneWay,
Path=AdornedElement.Parent.ActualWidth}"
WPF is quite powerful and flexible.
You can bind any property in any XAML tag to any property in any other XAML tag.
For example, you could write a test app that binds the Text property of an input box to the Text property of a label, so as you type something into the text box, the label would change automatically (assuming you use UpdateSourceTrigger=PropertyChanged). This is a direct XAML to XAML binding, with no C# in sight.
Similarly, you could bind the width of your error box to the width of the parent control, whatever that may be.
Google RelativeSource and AncestorType, this is a great link:
http://druss.co/2013/10/wpf-binding-examples/
See if you can grok how the Visual Tree and Logical Tree works in WPF, once you understand that, you will understand more of how binding works.
I'd also recommend using the free tool Snoop to look at the Visual Tree. XAML Spy is excellent, but not free.
Snoop can tell you if there is anything that has a bad binding at runtime (you set the filters up, and it will list all bad bindings).
You can use Snoop to get the full XAML path of your source (the XAML you wrote above), then get the full XAML path of the target (i.e. the ActualWidth of your Grid), then compare them: it may be quickly apparent that one is not the ancestor of the other, as they are on different branches of the visual tree, or that there is some other issue which is preventing a simple walk up the visual tree from working.
If you just want to get something working, as a proof of concept, try naming the target XAML grid using x:Name, and reference it by name instead of AncestorType.

Why does this ComboBox ignore the DataTemplate when SelectedItem is a ContentControl?

In our application we have a screen design feature which is comprised of a custom ScreenDesignPanel and a Property Grid with a ComboBox at the top which points to the selected item on the ScreenDesignPanel. This allows the user to select the UIElement via the ComboBox or via the mouse to set its properties. We achieve this by binding the ItemsSource of the ComboBox to the ScreenDesignPanel's Children collection, then binding their SelectedItems together. This works great.
However, for whatever reason, if the SelectedItem is a ContentControl or a subclass like Button the ItemTemplate specified for the ComboBox is ignored for the 'selected item area' but it is applied when displaying the item in the dropdown list. If the SelectedItem is not a ContentControl, the template is used in both cases.
This also is seemingly specific to the ComboBox. If we use any other selector control: ListBox, ListView, ItemsControl... even third-party ComboBox controls... they all work as expected, properly applying the DataTemplate. ComboBox is doing something internally which no other control is doing.
Note: Below is an over-simplified example for illustrative purposes of the issue only. It is not how we're actually using it as described above.
Also of note: In the DataTemplate for the ComboBox.ItemTemplate, we are only using properties (i.e. Foreground in the example), and are not displaying the DataContext (i.e. the actual ContentControl) itself. This is important because again, the actual control already exists on the ScreenDesignPanel and therefore can't be used for display in the ComboBox's ItemTemplate as it would have two parents which isn't allowed. In other words, it is being used purely as data here.
One last thing... we have a working solution in our app, which was to wrap the Children before binding it to the ComboBox.ItemsSource. However, I'm still curious as to why the ComboBox behaves the way it does which is SPECIFICALLY what I'm asking. (In other words, I'm not looking for other solutions to this design. We already have a working one. I'm looking for clarity on the odd behavior of the ComboBox itself.)
On to the code!
In the first example below, note how the data template is applied to everything in the dropdown, but the selected item area only uses a template if the selected item is not a ContentControl.
<ComboBox>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="I am the template" Foreground="{Binding Foreground}" />
</DataTemplate>
</ComboBox.ItemTemplate>
<!-- Four 'Data' items for example only -->
<TextBlock Text="I am a Red TextBox" Foreground="Red"/>
<ListBox Foreground="Purple">
<ListBoxItem>I am a Purple ListBox</ListBoxItem>
</ListBox>
<ContentControl Content="I am a Blue ContentControl" Foreground="Blue" />
<Button Content="I am a Button with Green text" Foreground="Green" />
</ComboBox>
This second example shows that it is completely acceptable and fully supported to use a UIElement as the content of a ContentPresenter and still use a DataTemplate (via ContentTemplate) so you can use it in a purely-data role, allowing the template itself to define the visual appearance without displaying the UIElement itself, which is used purely as data here.
<ContentPresenter>
<ContentPresenter.ContentTemplate>
<DataTemplate>
<TextBlock Text="I am the ContentTemplate" Foreground="{Binding Foreground}" />
</DataTemplate>
</ContentPresenter.ContentTemplate>
<ContentPresenter.Content>
<Button Content="I am the button" Foreground="Green" />
</ContentPresenter.Content>
</ContentPresenter>
Again, the issue is specific to a ComboBox. I want to find out why the data template isn't applied in that single case, and how to force it to be applied, if possible.
Of note, ComboBox does define SelectionBoxItemTemplate which is separate from the regular ItemTemplate but the rub is that is read-only so you can't set it. We really don't want to re-template the ComboBox as that can mess up proper theming.
Have you tried explicitly setting the DataTemplate to the ContentControl.ContentTemplate property?:
<UserControl.Resources>
<DataTemplate x:Key="DataTemplate">
<TextBlock Text="{Binding Content,
StringFormat='Displayed via template: {0}'}" />
</DataTemplate>
</UserControl.Resources>
...
<ContentControl Content="ContentControl"
ContentTemplate="{StaticResource DataTemplate}" />

Copying Xaml RichTextBlock text with InlineUIContainers

I am working with some RichTextBlock objects that contain InlineUIContainer elements. I would like to be able to select and copy all of the text including the text contained in the InlineUIContainer.
Currently, when I select all of the text in the block, the text contained in the InlineUIContainer objects are skipped.
Here is an example of what I'm creating:
<RichTextBlock IsTextSelectionEnabled="True">
<Paragraph FontSize="20">
<Bold>This text is selectable</Bold>
<InlineUIContainer FontFamily="Global User Interface">
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="11" VerticalAlignment="Top" Margin="0,0,-1,0">Super Script Text</TextBlock>
<HyperlinkButton ClickMode="Release" Style="{StaticResource NoMarginHyperlinkButtonStyle}">
Link
</HyperlinkButton>
</StackPanel>
</InlineUIContainer>
This text is also selectable
</Paragraph>
</RichTextBlock>
If I select all of the text from this piece of Xaml and copy/paste it in NotePad, I don't get the Super Script Text or the Link text.
Is there any way to get all of the text selected?
This is because HyperlinkButton is not part of the document API and in fact a UIElement wrapped in InlineUIContainer. There are 2 ways to handle this.
Switch to Windows 8.1 and Hyperlink which inherits from TextElement and copy will work just fine.
This is the hard way, if you must support this in Windows 8.
Remove the default Context menu items for the RichTextBlock and replace with your own Copy Command. Which should get the 2 TextPointers i.e. RichtextBlock.SelectionStart and RichTextBlock.SelectionEnd
Now with WPF we could get a TextRange within this range but winRT does not expose it, so you will need to do it in your code...
Get all the block within the RichTextBlock, and iterate through each to check if it's ContentStart and ContentEnd is within the RTB.SelectionStart and RTB.SelectionEnd if so then add them to a list.
Now it should be easy to extract all the Runs and Bold/Italics from this list and any InlineUIContainers hosting HyperlinkButtons.
2 This is not really a good way to go as it will be hard to allow for margins etc. on Paragraphs etc.

WPF highlighting

Anyone has any idea how to highlight in a textblock?
Basically i have 2 textblock and both have the same string. When I highlight part of the string in one of the textblock, the other textblock shows the same highlighted part as well. I am basically stuck at how to do the highlighting.
Thanks in advance
Do you have a TextBlock or a TextBox? I'm going to assume it's a TextBox, since TextBlock does not support text selection.
In that case, you can simply use data binding to keep this in sync.
<TextBox Name="text1" />
<TextBox Name="text2"
SelectionStart="{Binding Path=SelectionStart, ElementName=text1}"
SelectionLength="{Binding Path=SelectionLength, ElementName=text1}" />
This should ensure that the same area of text is selected in text2 when the user selects it in text1 and vice-versa.
EDIT See this answer for instructions on how to bind to these properties.
If you create a custom TextBox as described in the linked answer, your code would look something like this:
<SelectionBindingTextBox Name="text1" />
<SelectionBindingTextBox Name="text2"
BindableSelectionStart="{Binding Path=BindableSelectionStart, ElementName=text1}"
BindableSelectionLength="{Binding Path=BindableSelectionLength, ElementName=text1}" />

Categories