Named control exists on Windows 10 but not on Windows 8.1 - c#

I have the following code in a Windows 8.1 Store App. This code runs perfectly fine on Windows 10 but crashes on Windows 8.1. The second named control in MainPage.xaml.cs is null on Win 8.1 but not on Windows 10. It's not a timing issue as the named control still won't be populated in any subsequent event handler following the page load. What on earth is going on here?
To summarize, I have a ContentControl with a ContentPresenter defined in its Template. That ContentControl is then instantiated on a page, with a named child control (using "x:Name") as its Content. On Windows 10, that named control exists in code-behind. On Windows 8.1 it is null
MyUserControl1.xaml
<ContentControl
x:Class="App1.MyUserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<ContentControl.Template>
<ControlTemplate>
<ContentPresenter Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
</ContentControl.Template>
MainPage.xaml
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Name="TextBlock1"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextAlignment="Center"
FontSize="50"
TextWrapping="Wrap" />
<local:MyUserControl1 Grid.Column="1">
<TextBlock x:Name="TextBlock2"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextAlignment="Center"
FontSize="50"
TextWrapping="Wrap" />
</local:MyUserControl1>
</Grid>
MainPage.xaml.cs
using Windows.UI.Xaml.Controls;
namespace App1
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
TextBlock1.Text = "This works";
TextBlock2.Text = "This does not work because TextBlock2 is null";
}
}
}

Of course you cannot reference this. Your TextBlock2 is explicitly being set BY YOU to be the content of another control fundamentally out of scope. After rendering is complete, your TextBlock2 is no longer a child of your MainPage but instead a child of the ControlTemplate in your UserControl. Windows 10 is behaving EXACTLY how it should, and it appears you have discovered a bug in the Windows 8 rendering engine, if it worked.
One
There are a few workarounds. The first is the textbook approach of adding a property to your UserControl that adds access to this control. Because you are allowing the content to be dynamic, the operation inside that property (or method) would also need to be dynamic. Something like GetControl<TextBlock>("TextBlock1") which could hunt for you.
public bool TryGetControl<T>(string name, out T control)
{
try
{
var children = RecurseChildren(this.MyUserControl);
control = children
.Where(x => Equals(x.Name, name))
.OfType<T>()
.First();
return true;
}
catch
{
control = default(T);
return false;
}
}
public List<Control> RecurseChildren(DependencyObject parent)
{
var list = new List<Control>();
var count = VisualTreeHelper.GetChildrenCount(parent);
var children = Enumerable.Range(0, count - 1)
.Select(x => VisualTreeHelper.GetChild(parent, x));
list.AddRange(children.OfType<Control>());
foreach (var child in children)
{
list.AddRange(RecurseChildren(child));
}
return list;
}
Two
The second thing you could do is simply hunt for the control through the child hierarchy of the UserControl from the page itself. The logic would be the same as number one (above) but it would execute inside the page and not be part of your UserControl logic. You already do this sort of thing when you need to find the ScrollViewer in a ListView or maybe the inner Border in a Button for some reason.
It turns out I have already explained this in a blog article you can use as reference: http://blog.jerrynixon.com/2012/09/how-to-access-named-control-inside-xaml.html
Three
And here's a third, perhaps simplest way to do it. Handle the Loaded event of TextBlock1 in your MainPage and set a field to the value of the sender in the handler method. You can even cast it to TextBlock so everything is typed. This gives you simple access, but has the potential downside of timing. If you try to access the field value before it is set, you might find it is null. But, in most cases, this works the easiest.
So, that's three ways to handle it. I think it is very important that you recognize that this is EXPECTED behavior since a XAML element can have only one parent and you are setting that parent through the Content property of your ContentPresenter. That being said, it might be expected behavior, but it is not obviously intuitive.
Best of luck.

Related

WPF Databinding to second view

I have spend a little over a Day on this problem and i am absolutely Clueless.
If i click the button to show the Second View it Opens, but without Content.
I even get by a breakpoint in the View Model.
For this i have reduced everything to a Simple Textbox and Textblock that shut display the same Data, but they do not. They show nothing even after Typing into the Box the Block does not update.
But what ever i try the Databinding does not Work. Does anyone has an Idea?
Thanks in Advance
My second View
<Window x:Class="AoE4_BO_Overlay.Views.EditorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:AoE4_BO_Overlay.Views" xmlns:viewmodels="clr-namespace:AoE4_BO_Overlay.ViewModels" d:DataContext="{d:DesignInstance Type=viewmodels:EditorViewModel}"
mc:Ignorable="d"
Title="EditorView" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Path=FirstName}" Grid.Column="0" Grid.Row="2"/>
<TextBox Text="{Binding Path=FirstName , Mode=OneWay}" Grid.Column="0" Grid.Row="1"/>
</Grid>
My ViewModel
internal class EditorViewModel : Conductor<object>
{
private string _firstName = "Tom";
public EditorViewModel()
{
}
public string FirstName
{
get
{
return _firstName;
}
set
{
_firstName = value;
NotifyOfPropertyChange(() => FirstName);
}
}
}
How i call both of them
public void CreateBO_Click(object sender, RoutedEventArgs e)
{
EditorView createBO = new EditorView();
ActivateItemAsync(new EditorViewModel());
createBO.Show();
}
added Information
public partial class EditorView : Window
{
public EditorView()
{
DataContext = new EditorViewModel();
InitializeComponent();
}
}
You have two separate issues:
1. Typing into the textbox doesn't change the bound property:
This is expected, since you use OneWay binding explicitly. OneWay binding means the property updates the user interface, but not the other way around. So changing FirstName should update the TextBox, but changing the TextBox doesn't update FirstName.
Interestingly enough, if you just omit the Mode = OneWay part, it should work - since TextBoxes should use TwoWay binding by default. I recommend you define your TextBox binding explicitly as Mode = TwoWay
2. Your view initializes with an empty TextBlock / TextBox
This one is harder to pin down, since you don't show us where you set your DataContext. This usually happens to me when I set the DataContext AFTER InitializeComponent(), instead of before. You either set the DataContext before the binding is initialized (as part of InizializeComponent()), or you have to raise a NotifyPropertyChanged on your property to update the UI afterwards.
If this is not the cause, you might want to enable WPF binding errors in your output console - that usually gives you a good idea of where your bindings fail. Visual Studio has an option for that. It should be located here:
Tools -> Options -> Debugging -> Output Window -> WPF Trace Settings
-> Data Binding -> All
I believe what you are attempting here is to show your second View (EditorView) within the first one (and not as a pop-up - if you intend to have it as popup, use WindowManager instead of ActivateItemAsync).
One thing you need to change for making this possible is to ensure your second View is a UserControl and not a Window.
// EditorView.xaml.cs
public partial class EditorView : UserControl
// EditView.xaml
<UserControl x:Class="AoE4_BO_Overlay.Views.EditorView"
Also since your using the ActivateItemAsync, you would need to ensure that your FirstView contains a ContendControl with Name "ActiveItem".
// FirstView.xaml
<ContentControl x:Name="ActiveItem"/>
The call to ActivateItemAsync would use this control to load the View of your second ViewModel (EditorViewModel). With this in place, you could now use the ActivateItemAsync method to load the View.
public async Task CreateBO_Click(object sender, RoutedEventArgs e)
{
await ActivateItemAsync(new EditorViewModel());
}
Please note that method ActivateItemAsync supports asynchronous calls and it would be wise to call the method asynchronously.
Another point to note is that you do not need to specify the DataContext explicitly as seen in the OP if you are using Caliburn Micro and the View/ViewModels are stored in the recommended folder/namespaces structures. Caliburn Micro uses naming conventions to associate the appropriate view-viewmodel pairs. More information on the same could be found in the official documentation

UWP+Prism MVVM - How do I set the value of a view model property via XAML?

I have a property in a view model which I would like to be able to set via the XAML but I can't figure out how to do it.
I have a pretty basic user control (containing a list of items), two of which are to be placed on a page and I would like to be able to set one to be a 'Source' (defined by an enum) and one to be a 'Target'.
[The code below has been stripped down quite a bit so apologies if I've accidentally made some mistakes or missed something out.]
My enumeration is:
public enum ConversionSide
{
Source, // Convert something FROM whatever is here.
Target // Convert something TO whatever is here.
}
I have a page which looks like this:
<Page
x:Class="MyApp.Views.ConverterPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:MyApp.Models"
xmlns:my="using:MyApp.Controls"
xmlns:prismMvvm="using:Prism.Windows.Mvvm"
prismMvvm:ViewModelLocator.AutoWireViewModel="True"
Style="{StaticResource PageStyle}"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<my:SelectorPage Name="SourceSelector" Grid.Column="0" />
<my:SelectorPage Name="TargetSelector" Grid.Column="1" />
</Grid>
</Page>
...where SelectorPage is a user control (I've called it a 'Page' to make the Prism AutoWire work but that's not the issue here) containing a list of items (all working fine) which looks like this...
<UserControl
x:Class="MyApp.Controls.SelectorPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:MyApp.Models"
xmlns:my="using:MyApp.Controls"
xmlns:prismMvvm="using:Prism.Windows.Mvvm"
prismMvvm:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d">
<ListView
Grid.Column="0"
ItemsSource="{x:Bind ViewModel.MyList, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.MySelectedItem, Mode=TwoWay}">
<ListView.Header>
<TextBlock Margin="0,8,0,8" HorizontalAlignment="Center" FontStyle="Italic" Text="Header Text" />
</ListView.Header>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:MyListItem">
<my:MyListItemTemplate />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>
..with code behind as...
public sealed partial class SelectorPage : UserControl
{
private SelectorViewModel ViewModel => DataContext as SelectorViewModel;
public SelectorPage()
{
this.InitializeComponent();
}
}
SelectorViewModel looks like this...
public class SelectorViewModel : ViewModelBase
{
private ConversionSide _side;
public ConversionSide Side
{
get { return _side; }
set { SetProperty(ref _side, value); }
}
// Many lines have been omitted for 'clarity'.
}
I would like to be able to set the Side property of SelectorViewModel in XAML like this...
<my:SelectorPage Name="SourceSelector" Grid.Column="0" Side="Source" />
<my:SelectorPage Name="TargetSelector" Grid.Column="1" Side="Target" />
(Once Side has been set, I do not expect it to ever change.)
How can I do this?
I've looked at using a dependency property but I can't get it to change the property in SelectorViewModel. When I add one in SelectorPage it's visible in the XAML and I can set it but it doesn't actually do anything so I'm probably not using it right. Putting a dependency property in the view model doesn't sound right to me but I could be wrong.
I've had a look around the web - Microsoft documentation, blogs, articles, stack overflow, etc. - but I can't find anything that explains things well enough for me to figure out what I'm supposed to do. The writings I've found seem to be exclusively about getting information from a bound property - which I'm okay with - but what I'm after is setting a property from the XAML.
Can anyone give my any clues please? I don't know if I'm just a tiny step away from getting what I want or if I'm miles away.
This would set the Side property of the SelectorPage control to Source:
A view sets the property of a view model by two-way bind to it. For example, the following TextBox sets the string property of a view model called Test when you change the text in the TextBox:
<TextBox Text="{Binding Test, Mode=TwoWay}" />
So setting the property of a view model from the view typically applies to controls that handles some kind of input. Any default value of a source property should be defined in the view model:
private ConversionSide _side = ConversionSide.Source;
You shouldn't define the default values in the view.

Removing the reference to a user control in wpf

I have a wpf application which has a main window and menu. This main window has a panel, and on clicking the menu item i create an instance of the user control and load the panel with the control.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="" MinHeight="750" Height="Auto" MinWidth="1100" Width="Auto" WindowState="Maximized" ScrollViewer.VerticalScrollBarVisibility="Auto"
Loaded ="MainWindow_OnLoaded" Closing="Window_Closing">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility ="Auto" SizeChanged="ScrollViewer_SizeChanged">
<Grid Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="38"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Height="38" Width="Auto" Background="#09527B">
<Grid Margin="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="70"></ColumnDefinition>
</Grid.ColumnDefinitions>
</Grid>
</StackPanel>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="189"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<StackPanel>
<Expander Name="test" Header="Admin" Foreground="White" Margin="0,10,0,0">
<StackPanel Margin="20,0,0,0">
<Expander Header="Data" Foreground="White">
<StackPanel>
<TextBlock Text="Add/Edit UC1" Foreground="White" Margin="30,5,0,0" MouseDown="OpenUC1_MouseDown" MouseEnter="TextBlock_MouseEnter" MouseLeave="TextBlock_MouseLeave"/>
<TextBlock Text="Add/Edit UC2" Name="tbxBuild" Foreground="White" Margin="30,5,0,0" MouseDown="OpenUC2_MouseDown" MouseEnter="TextBlock_MouseEnter" MouseLeave="TextBlock_MouseLeave"/>
</StackPanel>
</Expander>
</StackPanel>
</Grid>
<StackPanel Grid.Column="1">
<Grid Name="pnlMain" Height ="Auto" VerticalAlignment="Top" HorizontalAlignment="Left">
</Grid>
</StackPanel>
</Grid>
</Grid>
</ScrollViewer>
</Window>
MainWindow.cs
private void OpenUC1_MouseDown(object sender, MouseButtonEventArgs e)
{
for (int i = 0; i < pnlMain.Children.Count; i++ )
{
pnlMain.Children.Remove(pnlMain.Children[i]);
}
using (UC2 _uc2= new UC2())
{
pnlMain.Children.Add(_uc2);
}
}
private void OpenUC2_MouseDown(object sender, MouseButtonEventArgs e)
{
for (int i = 0; i < pnlMain.Children.Count; i++ )
{
pnlMain.Children.Remove(pnlMain.Children[i]);
}
using (UC1 _uc1= new UC1())
{
pnlMain.Children.Add(_uc1);
}
}
My question is when I remove the control(UC1) from the main panel, when will that control be disposed?
Both the user control(UC1 and UC2) has the same view model attached to its data context. So i find that some of the methods in the removed user control(UC1) is called even though that is removed from the panel. The reason being, when a new instance of UC2 is created, there are some changes in the data model which in effect calls the dependent methods in UC1.
But if UC1 had been disposed this wouldn't happen. How can I make sure UC1 is disposed before instance of UC2 is created?
public UC1()
{
InitializeComponent();
this.DataContext = App.ViewModel.TestViewModel;
}
private void UC1_Unloaded(object sender, RoutedEventArgs e)
{
this.DataContext = null;
}
public UC2()
{
InitializeComponent();
this.DataContext = App.ViewModel.TestViewModel;
}
private void UC2_Unloaded(object sender, RoutedEventArgs e)
{
this.DataContext = null;
}
The unloaded method is not called immediately when the control is removed from the panel.
When I write and test code to dynamically add and remove a UserControl object from a window's visual tree, I find that the Unloaded event is raised just as expected.
In your own code example, there is at least one serious problem, and two incongruities:
The serious problem is how you are removing children. Your for loop is iterating by index through the children of the pnlMain object (a Grid). But removing any child invalidates the sequence of indexes! That is, the loop will first remove the child at index 0; this causes the child at index 1 to now become the child at index 0. But the loop increments the index before continuing, and will next remove the child at index 1. This child was originally at index 2. The code skips every other child (i.e. the ones originally at odd-numbered indexes), leaving half of them attached as children of the Grid.
Incongruity #1: I would expect a method with the phrase "OpenUC1" in the name to add an instance of UC1. However, your OpenUC1_MouseDown() method seems to be adding an instance of UC2 (and vice a versa for OpenUC2_MouseDown()). At the very least, there should be a comment in the code explaining why the code is different from what one might expect given the name of the method.
Incongruity #2: there is a using statement around the call to Add() when adding the UserControl objects. First, UserControl itself does not implement IDisposable, so unless your types have implemented that interface, that code is not even legal. Second, even if your UserControl subclasses do implement that interface, it does not seem like a very good idea to me to dispose an object that you've just created and which you are retaining in the visual tree (i.e. by adding it to the Grid's children).
Unfortunately, as I mentioned in my comment, without a good, minimal, complete code example that reliably reproduces your problem, it is impossible to say why your code does not behave as one would hope and/or expect it to. It is possible that any of the above points (but especially #1) are the cause of the behavior you're seeing, but I have no way to know for sure.
If after addressing those issues (or determining somehow that they are not problems…though if you can legitimately do that, I would argue that the code is still defective, in the sense that it's poor design), you find that your problem still exists, please edit your question so that it includes a good, minimal, complete code example that reliably reproduces the problem.
In the meantime, here is a simple code example that illustrates the basic behavior of the Unloaded event being raised just as expected when the object is removed from the visual tree. Note that while the correct way to remove all children from the Grid object's Children collection is to simply call the Clear() method (e.g. pnlMain.Children.Clear()), I have included an example of a explicit loop-based approach that does work.
XAML:
UserControl1.xaml
<UserControl x:Class="TestSO33289488UserControlUnloaded.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Unloaded="UserControl_Unloaded"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="UserControl" FontSize="36"/>
</Grid>
</UserControl>
MainWindow.xaml
<Window x:Class="TestSO33289488UserControlUnloaded.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">
<StackPanel>
<Button x:Name="button1" Content="Add UserControl"
HorizontalAlignment="Left" Click="Button_Click"/>
<Grid x:Name="grid1"/>
</StackPanel>
</Window>
C#:
UserControl1.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace TestSO33289488UserControlUnloaded
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
{
MessageBox.Show("UserControl.Unloaded was raised");
}
}
}
MainWindow.xaml.cs
using System.Windows;
namespace TestSO33289488UserControlUnloaded
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private bool _removeUserControl;
private void Button_Click(object sender, RoutedEventArgs e)
{
if (_removeUserControl)
{
//grid1.Children.Clear();
// Calling Clear() is better, but if you really want to loop,
// it is possible to do correctly. For example:
while (grid1.Children.Count > 0)
{
grid1.Children.RemoveAt(grid1.Children.Count - 1);
}
button1.Content = "Add UserControl";
}
else
{
grid1.Children.Add(new UserControl1());
button1.Content = "Remove UserControl";
}
_removeUserControl = !_removeUserControl;
}
}
}
Quote from an MSDN forum entry about Loaded/Unloaded events:
The events are raised asynchronously, so there might be some delay
between the action that causes the event and the event itself. The
events are effectively put into a list and a task is added to the
dispatcher's queue. When that task runs, it raises the events on the
list.
So the answer is you can't predict when exactly these events will raised and you shouldn't expect that they will be called immediately after you removed a control from it's parent.
It's kinda difficult to give you a proper solution without seeing the full project, but here's a quick and dirty solution: rather than making sure that the given user controls' events are fired in time let's check the Parent property of the UC1/UC2 object before running the method. If the property is null then the UC1/UC2 object was removed and you should not execute that method.
But let me point out some problems with this code:
What's the point of the using block in the MouseDown event handlers? You create a user control object, add it to the panel and then immediately after that you call the Dispose method on it? (that's what the using block does in C#)
You don't need a for loop to remove all the children elements from a Panel control like a Grid. You can do that in one line. pnlMain.Children.Clear();

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

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 :
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;
}
}
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.
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.
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>
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.

Change between pages in WPF

I want to make a layout like the one used in any website - the header, sidebar and footer stay the same but the center part. I have multiple pages/windows to show in a wpf blend C# application and they are totally different. For example, stackoverflow has a layout for the homepage and another one for each Question. Here's another exemple:
I had to do that in a previous project and I used a single grid layout and then, for each page, I had to hide() all of them and show that each one on top -
What's the trick? How can I do the same thing in a wpf application? In a typical C# application I would have to open a child window each time but that seems ugly these days.
Thank you in advance!
If you are going to use Pages in WPF, then you will need to read the Navigation Overview page on MSDN. In short however, you can navigate between Pages in a WPF Application by using the NavigationService Class. To change the page from code behind, you could do something like this:
NextPage page = new NextPage();
NavigationService.Navigate(page);
To let the users change the Page, you can use the Hyperlink Class in your Pages:
<Hyperlink NavigateUri="pack://application:,,,/AppName;component/Pages/NextPage.xaml">
Navigate to Next Page
</Hyperlink>
To get your desired page setup, you will have to load your Pages into a Frame, which you can then layout wherever you like in MainWindow.xaml:
<Frame Source="pack://application:,,,/AppName;component/Pages/SomePage.xaml" />
Sounds like you need a custom usercontrol and some databinding.
You can declare DataTemplates in XAML as resources with the model type as key, so that WPF chooses the correct DataTemplate automatically:
Have a main ViewModel, which exposes a ImageSourceViewModel property. This property would either return a CameraSourceViewModel or a FileSourceViewModel, as appropriate.
In your page, the DataContext would be the main ViewModel, and you'd have XAML like this:
Then,
<Page x:Class="Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:my="clr-namespace:WpfApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="Page1">
<Page.Resources>
<DataTemplate DataType="{x:Type my:CameraSourceViewModel}">
<my:CameraSourceView/>
</DataTemplate>
<DataTemplate DataType="{x:Type my:FileSourceViewModel}">
<my:FileSourceView/>
</DataTemplate>
</Page.Resources>
<Grid>
<ContentControl Content="{Binding ImageSourceViewModel}"/>
</Grid>
I should point out that this example uses the MVVM pattern to allow the viewmodel layer to decide on the content in the middle. Hopefully this is clear enough, if not, give me a shout and I'll try to expand it!
Let's say I have main view model where I've created a CurrentPage property that will tell which page you want to display.
/// <summary>
/// Returns the page ViewModel that the user is currently viewing.
/// </summary>
public ViewModelBase CurrentPage
{
get { return _currentPage; }
private set
{
if (value != _currentPage)
{
if (_currentPage != null)
_currentPage.IsCurrentPage = false;
_currentPage = value;
if (_currentPage != null)
_currentPage.IsCurrentPage = true;
RaisePropertyChanged(() => CurrentPage);
}
}
}
And in your xaml you can bind your page under some control. Let's say I am doing it inside a Border element.
<!-- CURRENT PAGE AREA -->
<Border Background="White" Grid.Column="1" Grid.Row="0">
<HeaderedContentControl Content="{Binding Path=CurrentPage}"
Header="{Binding Path=CurrentPage.DisplayName}" />
</Border>
You can define view to your view model in resources just like this:
(partially complete XAML)
<UserControl x:Class="BAT.View.BATWizardView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:view="clr-namespace:BAT.View"
xmlns:viewmodel="clr-namespace:BAT.ViewModel"
mc:Ignorable="d"
d:DesignHeight="350" d:DesignWidth="600">
<UserControl.Resources>
<!-- These four templates map a ViewModel to a View. -->
<DataTemplate DataType="{x:Type viewmodel:MyComparisonViewModel1}">
<view:MyView1 />
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodel:MyComparisonViewModel2}">
<view:MyView2 />
</DataTemplate>
</UserControl.Resources>
<Grid>
<Border Background="White" Grid.Column="1" Grid.Row="0">
<HeaderedContentControl Content="{Binding Path=CurrentPage}"
Header="{Binding Path=CurrentPage.DisplayName}" />
</Border>
</Grid>
</UserControl>
See if that helps.

Categories