WPF ContextMenu = {x:Null} but still shows menu inside ContentControl
Asked Answered
E

4

8

I need disable standard ContextMenu of TextBox. I've created a new WPF project and added the following:

<Window x:Class="WpfApplication3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
       <ContentControl>
           <ContentControl.ContentTemplate>
               <DataTemplate>
                    <TextBox ContextMenu="{x:Null}" VerticalAlignment="Top" HorizontalAlignment="Left" Width="50"></TextBox>
                </DataTemplate>
           </ContentControl.ContentTemplate>
       </ContentControl>
    </Grid>
</Window>

But this is what i get :

enter image description here

The following code works fine :

<Grid>
     <TextBox ContextMenu="{x:Null}" VerticalAlignment="Top" HorizontalAlignment="Left" Width="50"></TextBox>
</Grid>

Why is this happening?

Update.

According to the accepted answer I've created a class derived from TextBox in order to be able to show parents ContextMenu.

    public class TextBoxNoMenu: TextBox
    {
        public TextBoxNoMenu()
        {
            ContextMenu = null;
        }
    }
Elwaine answered 15/10, 2014 at 14:35 Comment(0)
J
3

Why is this happening?

This is an interesting case of a control's behavior changing depending on where/how a property is set.

TextBox provides its own context menu by default. The only time it won't do this is when you explicitly set the local value of ContextMenu to null. This is what happens in your simple example where the TextBox is directly within in the Grid.

However, when you set a property inside a template, you're not actually setting a local value; you're setting a "parent template" value. If you inspect the value with DependencyPropertyHelper.GetValueSource(), you'll see the base value source is ParentTemplate instead of Local. Thus, the menu still gets overridden.

See Dependency Property Value Precedence for more information about the different kinds of dependency property value sources.

@OmegaMan's suggestion of assigning a 'hidden' context menu seems to work pretty well.

Janel answered 15/10, 2014 at 15:42 Comment(1)
My initial goal was to open parent's ContextMenu. So, collapsing doesn't solve actual problem. However, when I saw you answer I've created a class TextBoxNoMenu derived from TextBox and nulled its context menu.Elwaine
K
2

This seems to be a running issue where X:Null does not 'turn off' the default context menu. A better way would be to change it's visiblity:

<TextBox.ContextMenu>
    <ContextMenu Visibility="Collapsed"/>
</TextBox.ContextMenu>
Kesler answered 15/10, 2014 at 15:42 Comment(0)
M
1

Note that while you mayhave disabled the ContextMenu on TextBox, if it's in another control, you may actually be seeing the ContextMenu of such a wrapper. Try Snooping it to see more specifically this sort of behaviour.

Note also that many of the default Control Templates throughout WPF can cause issues such as these by adding their own child objects. Seeing the default template for TextBox uses a Border and then <ScrollViewer Margin="0" x:Name="PART_ContentHost" />, you're likely seeing the ContextMenu of a child object if TextBox.

Macymad answered 15/10, 2014 at 14:45 Comment(2)
Code above is everything I have in the solution. So, the only wrapper is ContentControl. Moreover, if I set ContextMenu of the ContentControl to null I will get the same wrong behavior.Elwaine
Assuming you're not looking at the TextBox adding some children of it's own. Updating my answer accordingly.Macymad
G
0

I had a similar issue, but I was generating my controls programmatically, and my parent control is a dockpanel. Based on the accepted answer, I decided to set the null value in the code behind.

        <Grid>
            <DockPanel>
               <TextBox Name="txtBox" VerticalAlignment="Top" HorizontalAlignment="Left" Width="50"></TextBox>
            </DockPanel>
        </Grid>

and then

            private void Window_Loaded(object sender, RoutedEventArgs e)
            {
                txtBox.ContextMenu = null;
            }

EDIT: I felt this was kind of a haphazard answer, as it doesn't fully or directly solve this question. I did some digging and if you implement the method found in the answer to This Question you can find the textbox in the code-behind.

So, if you have this

        <Grid>
           <ContentControl>
              <ContentControl.ContentTemplate>
                 <DataTemplate>
                    <TextBox Name="txtBox" VerticalAlignment="Top" HorizontalAlignment="Left" Width="50"></TextBox>
                 </DataTemplate>
              </ContentControl.ContentTemplate>
           </ContentControl>
        </Grid>

Then you should be able to find your textbox by name (txtBox in this case) and set the context menu to null

                TextBox myTextBox = FindChild<TextBox>(Application.Current.MainWindow, "txtBox");

                myTextBox.ContextMenu = null;

Personally I'd prefer this to creating a new class with inheritance, but whatever works for you. This still doesn't answer "Why is this happening?" but I think the accepted answer does a good job of that.

Gainsay answered 13/11, 2015 at 17:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.