RelativeSource and Popup - wpf

The problem is that RelativeSource does not work in the following case. I use silverlight 5.
//From MainPage.xaml
<Grid x:Name="LayoutRoot" Background="White" Height="100" Width="200">
<Popup IsOpen="True">
<TextBlock Text="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType=Grid}}" />
</Popup>
</Grid>
//From MainPage.xaml.cs
public MainPage()
{
InitializeComponent();
DataContext = "ololo";
}
If I set a breakpoint on the binding, I'll get Error:
System.Exception: BindingExpression_CannotFindAncestor.
If I use ElementName=LayoutRoot instead of RelativeSource, everything will be OK.
Why does the relative source binding not work?

Popup is like ContextMenu , ToolTip controls , They are not added to the VisualTree. For this you will have to do like
<Grid x:Name="LayoutRoot" Height="100" Width="200" Background="Black">
<Popup Grid.Row="0" x:Name="popup" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Mode=Self}}">
<TextBlock Text="{Binding DataContext, ElementName=popup}" Background="Red" Width="30" Height="30" />
</Popup>
</Grid>
public MainWindow()
{
InitializeComponent();
DataContext = "abcd";
popup.PlacementTarget = LayoutRoot;
}
I hope this will help.Not like in case of ContextMenu or Tooltip , here you will also have to specify the PlacementTarget.

You can make small hack: setup DataContext via resources.
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="DataContext" Value="{Binding ElementName=myGrid, Path=DataContext}" />
</Style>
</Grid.Resources>

As others have mentioned, it's because the Popup is not part of the visual tree. Instead, you can use the Popup's PlacementTarget property to get back to the visual tree:
<Grid x:Name="LayoutRoot" Background="White" Height="100" Width="200">
<Popup IsOpen="True">
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Popup}},
Path=PlacementTarget.DataContext}" />
</Popup>
</Grid>

Popups are not part of the visual tree.
Relative Source "Gets or sets the binding source by specifying its location relative to the position of the binding target (MSDN)". Since Popups are not part of the visual tree of the control that is showing it, it will not be able to resolve anything outside of the popup.

Related

MVVM How to bind command to ContextMenu

I am completely confused using relativeSource and ancestorLevel.
Relative source is used for to get source from another elements. But to do that successfully you have to count at what level is that element. (How to debug?) It is most confusing part in WPF.
In my example i have context menu that i want to bind datasource and then command. How would binding must be to get command in my vm? Thank you
<Page.DataContext>
<PDB:UsersViewModel x:Name="vm"/>
</Page.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!--Page Header info content-->
<Grid Grid.Row="0">
<TextBlock Text="{Binding SelectedUser.Name}"/>
<TextBlock Text="{Binding ElementName=myGrd, Path=CurrentColumn.DisplayIndex}"/>
</Grid>
<!--Datagrid content-->
<DataGrid x:Name="myGrd"
SelectionMode="Single"
SelectionUnit="Cell"
CurrentItem="{Binding SelectedUser, Mode=TwoWay}"
CurrentColumn="{Binding CurrentColumn, Mode=TwoWay}"
IsReadOnly="True"
Grid.Row="1"
ItemsSource="{Binding FilteredUserList}"
AutoGenerateColumns="True"
CanUserAddRows="False"
>
<DataGrid.Resources>
<ContextMenu x:Key="ContextMenu">
<ContextMenu.Items>
<MenuItem Header="{Binding
RelativeSource={RelativeSource
FindAncestor,
AncestorType={x:Type Page},
AncestorLevel=4}, Path=vm}" />
</ContextMenu.Items>
</ContextMenu>
</DataGrid.Resources>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="ContextMenu" Value="{StaticResource ContextMenu}"/>
</Style>
</DataGrid.CellStyle>
</DataGrid>
</Grid>
You can't use RelativeSource in ContextMenu, because the menu is not a part of the visual tree. However this can be avoided by using Binding Source and x:Reference.
I assume your ViewModel looks like this
public class UserViewModel
{
public string Header { get; set; }
public ICommand MyCommand { get; }
... more code
}
Now let's bind Header and MyCommand properties of the VM
<ContextMenu x:Key="ContextMenu">
<ContextMenu.Items>
<MenuItem Header="{Binding Header, Source={x:Reference vm}}"
Command="{Binding MyCommand, Source={x:Reference vm}}"/>
</ContextMenu.Items>
</ContextMenu>
The important part is to have the ViewModel somewhere in the visual tree and set its x:Name, just like you've done in your example
<Page.DataContext>
<PDB:UsersViewModel x:Name="vm"/>
</Page.DataContext>
If you still want to know more about RelativeSource, this question seems to have the same problem as you. Basically Path of the binding has to be DataContext.MyViewModelProperty and the RelativeSource of the binding must be and element with DataContext set to the ViewModel.

Binding UserControl Width To A TextBox Input

It's my first time using the MVVM pattern and I have a bit of trouble understanding how everything ties together.
I have a UserControl with a Textbox element which should change the Width of said UserControl based on it's input.
I'm facing two problems:
For my idea to work, I need to change and bind to d:DesignWidth and my ColumnDefinition Width. How and where do I implement those changes? Based on my knowledge of MVVM the View (in this case my UserControl) is controlled by a ViewModel for said UserControl. Is it nessesary to implement one or is it possible to bind directly to both properties? I know I can name my ColumnDefinition with x:Name="MyColumnDefinition" but is the same possible for the actual UserControl Width?
mc:Ignorable="d"
d:DesignHeight="60" d:DesignWidth="170">
I have an ObservableCollection filled with two different UserControls and I want the Controls not to overlap when I display them. I use a ListBox element to display the ObservableCollection and implement the different UserControls over DataTemplates with a DataTemplateSelector. This works fine now but I'm worried if I dynamically change the Control Width that it will just overlap the next Control in the list. How do I ensure this won't happen?
Below is the code I have for now for the UserControl:
<Border Background="LightGray" CornerRadius="6">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<Button VerticalAlignment="Top" HorizontalAlignment="Right" Grid.Column="2" Grid.Row="0"
BorderThickness="0" Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=DeleteCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=DeleteCommandParameter}">
<Rectangle Width="8" Height="8" Fill="White">
<Rectangle.OpacityMask>
<VisualBrush Visual="{StaticResource appbar_close}" Stretch="Fill" />
</Rectangle.OpacityMask>
</Rectangle>
</Button>
<TextBlock Grid.Column="1" Grid.Row="0" FontSize="12" Margin="0,4,0,18" Foreground="White" HorizontalAlignment="Center" Grid.RowSpan="2">Delay</TextBlock>
<TextBox Grid.Column="1" Grid.Row="1" Width="46" Margin="0,4,0,16" HorizontalAlignment="Center" Grid.RowSpan="2"
Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=Delay.MiddlePosition, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Grid.Column="1" Grid.Row="2" FontSize="8" Margin="20,5,20,5" Foreground="Gray" HorizontalAlignment="Center">[s]</TextBlock>
</Grid>
</Border>
Edit:
ListBox-XAML to hold the other UserControls (I'm trying to build an Axis which can be filled with custom Positioning- and DelayControls:
<ListBox Name="Test" SelectionMode="Single" Grid.Column="1"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=BlockList}"
ItemTemplateSelector="{StaticResource BlockTemplateSelector}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Focusable" Value="False"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
End result should look kind of like this, but with differently sized Positioning and Delay blocks:
Check this code will help you to set width of one control to other control.
<Border>
<Grid x:Name="grv">
<TextBox Width="{Binding ElementName=grv,
Path=ActualWidth}">
</TextBox>
</Grid>
</Border>
I struggeled quite a while to figure out how to address your issue and even though I am not completely happy with the outcome, I managed to solve it.
First I create a ListBox with a DummyList, which contains Model-Objects called 'UserControlModel' with a singe Property 'modelWidth', from which I create my UserControls with their default size.
<ListBox ItemsSource="{Binding SimpleList, UpdateSourceTrigger=PropertyChanged}" Grid.Row="1" Width="Auto" Height="200">
<ListBox.ItemTemplate>
<DataTemplate>
<osv:UserControl1 Width="{Binding modelWidth}" OnTextValidated="UserControlSizeChangeEvent"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
OnTextValidated is a RoutedEvent to hand up the KeyDown-Event from my Textbox to my Window(which i will show later)
The UserControl1.xaml then adds the textbox
<TextBox Width="60" Height="30" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" KeyDown="TextBox_KeyDown" Text="{Binding myText, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"></TextBox>
with a KeyDown event and a textbinding.
private void TextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Return)//press enter to change
{
if (double.TryParse(myText, out double d) == true)
{
if (d >= 50) //minimum width, so i won't set it to 0 by accident
{
myWidth = d; //set the new Width
OnTextValidated(this, new RoutedEventArgs()); //tell window to resize the UserControl
}
}
}
}
Once I validated the new size is neither wrong nor too small i call a RoutedEventHandler
private RoutedEventHandler m_OnTextValidated;
/// <summary>
///
/// </summary>
public RoutedEventHandler OnTextValidated
{
get { return m_OnTextValidated; }
set
{
m_OnTextValidated = value;
OnPropertyChanged("CustomClick");
}
}
now i can bind on this like shown above.
next i have to do is passing down my event from the xaml.cs to the MinWindowViewModel
//Variables
private MainWindowViewModel m_DataContext;
//Constructor
DataContext = new MainWindowViewModel ();
m_DataContext = (MainWindowViewModel)this.DataContext;
private void UserControlSizeChangeEvent(object sender, RoutedEventArgs e)
{
if (m_DataContext != null)
{
m_DataContext.UserControlSizeChangeEvent(sender, e);
}
}
and finally update the size of my object in my code behind
public void UserControlSizeChangeEvent(object sender, RoutedEventArgs e)
{
UserControl1 uc = sender as UserControl1;
uc.Width = uc.myWidth;
}
Note: Although this works quite fine i'd be much happier if i found a way to change the width of the model instead of the object, so they would still have the same width in case the list is redrawn.
I also didn't use a MVVM pattern for my UserContrl, so you'll have to pass the event from your xaml.cs to your viewmodel first like I did for the MainWindow

Selecting listbox item inside datatemplate

I have a listbox that uses a data template. The template is very simple and consists of an Image and a TextBlock. They are both bound to an XML document. The template is as follows:
<Window.Resources>
<XmlDataProvider x:Key="NinjaData" Source="\Data\Ninjas.xml" XPath="/Ninjas"/>
<DataTemplate x:Key="NinjaDataTemplate">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0">
<Image Source="{Binding XPath=ImageFile}" Margin="5" Width="50"/>
<TextBlock Text="{Binding XPath=#Name}" Margin="5" FontFamily="OCR A Std" Foreground="#FF9114" FontSize="14" Name="NinjaName"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</Window.Resources>
The Listbox is as follows:
<ListBox Background="{x:Null}" BorderBrush="{x:Null}"
ItemsSource="{Binding Source={StaticResource NinjaData}, XPath=Ninja}"
ItemTemplate="{StaticResource NinjaDataTemplate}"
>
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Gray"></SolidColorBrush>
</ListBox.Resources>
<ListBox.Effect>
<DropShadowEffect ShadowDepth="1" Color="#FF282828"/>
</ListBox.Effect>
</ListBox>
I'm having trouble working out how to retrieve the content of the TextBlock inside the data template when I click on it in the listbox. I want to display it in a label somewhere else on the page, but I can't work out how to access that textblock.
Can anyone help and point me in the right direction? I feel like I'm going round in circles at the moment.
Thanks for any help.
As you have a ListBox you can use the selection, just name the ListBox and bind to the SelectedItem:
<ListBox Name="lb" ... />
<Label DataContext="{Binding SelectedItem, ElementName=lb}"
Content="{Binding XPath=#Name}"/>
This does not retrieve the Text of the TextBlock in the template but gets it from the same source as the template. (You could actually target the TextBlock in the Template itself but i would not recommend it)
Add a MouseLeftButtonUp event handler to your textblock inside your datatemplate like :
<TextBlock Text="{Binding XPath=#Name}" Margin="5" FontFamily="OCR A Std" Foreground="#FF9114" FontSize="14" Name="NinjaName" MouseLeftButtonUp="TextBlock_MouseLeftButtonUp"/>
Now your TextBlock_MouseLeftButtonUp should look like:
private void TextBlock_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
TextBlock txt = sender as TextBlock;
}
And now do whatever you like to do with txt

How do I access elements that have been dynamically assigned to a control in the form of an XAML resource?

I have the following resource in my window that declares how a certain kind of TabItem should look like.
<Window.Resources>
<StackPanel x:Key="TabSearchContents" x:Shared="False"
Orientation="Vertical">
<Border
BorderThickness="3"
BorderBrush="Purple">
<TextBlock
Text="SEARCH BOOKS"
FontFamily="Verdana"
FontSize="25"
Foreground="Blue"
HorizontalAlignment="Center" />
</Border>
<StackPanel
Height="30"
Orientation="Horizontal"
Margin="5">
<TextBox
x:Name="txtSearch"
Width="650"
FontFamily="Comic Sans MS"
Foreground="Chocolate" />
<Button
x:Name="btnSearch"
Width="100"
Content="Go!"
Click="BtnSearch_Click" />
</StackPanel>
<Grid x:Name="gridResults">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="450"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="0" VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="itmsSearch" ItemsSource="{Binding}" Padding="4"
ItemTemplate="{StaticResource SearchResultItemDT}">
</ItemsControl>
</ScrollViewer>
<StackPanel x:Name="stkpnlDetails">
</StackPanel>
</Grid>
</StackPanel>
</Window.Resources>
Then, in my code-behind, I dynamically create a tab and assign to the TabControl that is already present in my window.
void BtnNewTab_Click(object sender, RoutedEventArgs e)
{
TabItem tb = new TabItem();
tb.Content = this.Resources["TabSearchContents"];
tb.DataContext = _bridge.SearchBooksByTitle("e");
tb.Header = "Wuttp yo!";
Button btnGo = ((Button)tb.FindName("btnSearch"));
ItemsControl i = (ItemsControl)tb.FindName("itmsSearch");
btnGo.Resources.Add("ResultList", i);
daTabs.Items.Add(tb);
tb.Focus();
}
I want to access the btnSearch Button that is declared in my XAML resource.
As it is, this code throws an exception since btnGo turns out to be null (as well as i) since it can't find the expected control via FindName().
I read about the RegisterName() method, but it requires a reference to an instance of the required control... which I don't have.
I dont think you should define your button like this, try defining it in a style, creating a button and assigning the button that style, i think you will be able to get what you are going for this way.
myTheme.xaml
<ResourceDictionary
<Style x:Key="btnSearch" TargetType="{x:Type Button}">
<Setter Property="Width" Value="100"/>
<Setter Property="Content" Value="Go!"/>
<Setter Property="Click" Value="btn_Click"/>
</Style>
ResourceDictionary/>
myCode.cs
Button btnGo = new Button;
btnGo.Style = "{DynamicResource btnSearch}";
Hope this helps,
Eamonn

ContextMenu.PlacementTarget is not getting set, no idea why

<DataTemplate x:Key="_ItemTemplateA">
<Grid Tag="{Binding Path=DataContext.Command, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource ContentTemplateB}" Grid.Row="0" />
<ContentControl Name="uiContentPresenter" Content="{Binding ContentView}" Grid.Row="1" Height="0" />
<ContentControl DataContext="{Binding IsContentDisplayed}" DataContextChanged="IsDisplayed_Changed" Visibility="Collapsed" />
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Text"
Command="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
CommandParameter="{Binding}" />
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
The above data template is applied to an ItemsControl. The issue is that for the ContextMenu that is specified for the Grid, the PlacementTarget property is never actually getting set to anything so I cannot get to the Tag property of the Grid which is necessary for passing the Command that should execute on the parent UserControl down to the context menu. I've based this approach off of similar examples such as this: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/0244fbb0-fd5f-4a03-bd7b-978d7cbe1be3/
I've not been able to identify any other good way to pass this command down. This is setup this way because we are using an MVVM approach so the command we have to execute lives in the View Model of the user control this template is applied in. I've tried explicitly setting the PlacementTarget in a few different ways but it still always shows up as not set.
I realise that this is old and answered, but it doesn't seem properly answered. I came across a similar post and left a full answer. You might like to take a look as you can get it working with just a few adjustments to your code.
First, name your view UserControl... I generally name all of mine This for simplicity. Then remembering that our view model is bound to the DataContext of the UserControl, we can bind to the view model using {Binding DataContext, ElementName=This}.
So now we can bind to the view model, we have to connect that with the ContextMenu.DataContext. I use the Tag property of the object with the ContextMenu (the PlacementTarget) as that connection, in this example, a Grid:
<DataTemplate x:Key="YourTemplate" DataType="{x:Type DataTypes:YourDataType}">
<Grid ContextMenu="{StaticResource Menu}" Tag="{Binding DataContext,
ElementName=This}">
...
</Grid>
</DataTemplate>
We can then access the view model properties and commands in the ContextMenu by binding the ContextMenu.DataContext property to the PlacementTarget.Tag property (of the Grid in our example):
<ContextMenu x:Key="Menu" DataContext="{Binding PlacementTarget.Tag, RelativeSource=
{RelativeSource Self}}">
<MenuItem Header="Delete" Command="{Binding DeleteFile}" CommandParameter=
"{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource
AncestorType=ContextMenu}}" CommandTarget="{Binding PlacementTarget,
RelativeSource={RelativeSource Self}}" />
</ContextMenu>
Note the binding on the MenuItem.CommandTarget property. Setting this ensures that the target element on which the specified command is raised is the PlacementTarget, or the Grid in this case.
Also note the CommandParameter binding. This binds to the DataContext of the PlacementTarget, or the Grid in this case. The DataContext of the Grid will be inherited from the DataTemplate and so your data item is now bound to the object parameter in your Command if you're using some implementation of the ICommand interface:
public bool CanExecuteDeleteFileCommand(object parameter)
{
return ((YourDataType)parameter).IsInvalid;
}
public void ExecuteDeleteFileCommand(object parameter)
{
Delete((YourDataType)parameter);
}
Or if you are using some kind of RelayCommand delegates directly in your view model:
public ICommand Remove
{
get
{
return new ActionCommand(execute => Delete((YourDataType)execute),
canExecute => return ((YourDataType)canExecute).IsInvalid);
}
}
We have the same problem, but it works randomly. A contextmenu inside the controltemplate in a style for a listbox. We have tried to move the contextmenu to different levels inside the template but the same error occurs.
We think it might be connected to the refreshing of our ICollectionView that is the itemssource of the ListBox.
It seems that when the view refreshes the relative source binding inside the contextmenu is being evaluated before the PlacementTarget is being set.
It feels like a bug in either collectionviewsource or the ContextMenu of WPF...
Here is a working standalone XAML-only example based on your test case: a ContextMenu that retrieves a Command from the DataContext of its PlacementTarget using a Tag. You can reintroduce portions of your code until it stops working to try to find where the problem is:
<Grid>
<Grid.Resources>
<PointCollection x:Key="sampleData">
<Point X="10" Y="20"/>
<Point X="30" Y="40"/>
</PointCollection>
<DataTemplate x:Key="_ItemTemplateA">
<Grid Tag="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DockPanel}}}">
<TextBlock Text="{Binding X}"/>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding}"/>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
</Grid.Resources>
<DockPanel DataContext="{x:Static ApplicationCommands.Open}">
<ListBox ItemTemplate="{StaticResource _ItemTemplateA}" ItemsSource="{StaticResource sampleData}"/>
</DockPanel>
</Grid>
In this post,
ContextMenu.PlacementTarget is filled up from ContextMenuService.PlacementTarget when you do right click with mouse on button.
It means ContextMenu.PlacementTarget is filled up when the menu is shown up. You can check that by snoop.
EDIT 1
This code works fine.
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" BasedOn="{StaticResource DataGridRowBaseStyle}">
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=DataContext}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu Style="{StaticResource ContextMenuStyle}"
ItemContainerStyle="{StaticResource MenuItemStyle}">
<MenuItem Header="Enable" Command="{Binding Path=PlacementTarget.Tag.(viewModels:PrinterListPageViewModel.EnableCommand), RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>

Resources