Ive noticed this a couple of times when using menus with commands, they are not very dynamic, check this out. I am creating a menu from a collection of colours, I use it to colour a column in a datagrid. Anyway when i first bring up the menu (its a context menu) the command parameter binding happens and it binds to the column that the context menu was opened on. However the next time i bring it up it seems wpf caches the menu and it doesnt rebind the command parameter. so i can set the colour only on the initial column that the context menu appeared on.
I have got around this situation in the past by making the menu totally dynamic and destroying the collection when the menu closed and forcing a rebuild the next time it opened, i dont like this hack. anyone got a better way?
<MenuItem
Header="Colour"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:ResultEditorGrid}}, Path=ColumnColourCollection}"
ItemTemplate="{StaticResource colourHeader}" >
<MenuItem.Icon>
<Image
Source="{StaticResource ColumnShowIcon16}" />
</MenuItem.Icon>
<MenuItem.ItemContainerStyle>
<Style
TargetType="MenuItem"
BasedOn="{StaticResource systemMenuItemStyle}">
<!--Warning dont change the order of the following two setters
otherwise the command parameter gets set after the command fires,
not mush use eh?-->
<Setter
Property="CommandParameter">
<Setter.Value>
<MultiBinding>
<MultiBinding.Converter>
<local:ColumnAndColourMultiConverter/>
</MultiBinding.Converter>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type DataGridColumnHeader}}" Path="Column"/>
<Binding Path="."/>
</MultiBinding>
</Setter.Value>
</Setter>
<Setter
Property="Command"
Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:ResultEditorGrid}}, Path=ColourColumnCommand}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
The problem is that ContextMenu's are apparently the root of their own visual tree I read somewhere that it takes the datacontext its parent, but only once on loading, so if the parents datacontext changes the menuitems does not. (unfortunately I can't find a link for that right not)
I have encountered this problem before, and what I did was use Josh Smith's Virtual Branch Pattern. It's fairly technical but the article helped me understand really well what was going on with this visual tree nonsense.
Essentially you create this bridge that binds to the view's datacontext. The bridge is created as a static resource, allowing you to bind to it from the context menu even if it is outside the visual tree.
Add this to your xaml:
<Window.Resources>
<!-- This is the "root node" in the virtual branch
attached to the logical tree. It has its
DataContext set by the Binding applied to the
Window's DataContext property. -->
<FrameworkElement x:Key="DataContextBridge" />
</Window.Resources>
<Window.DataContext>
<!-- This Binding sets the DataContext on the "root node"
of the virtual logical tree branch. This Binding
must be applied to the DataContext of the element
which is actually assigned the data context value. -->
<Binding
Mode="OneWayToSource"
Path="DataContext"
Source="{StaticResource DataContextBridge}"
/>
</Window.DataContext>
This is the bridge I spoke of. It takes the datacontext and __pushes it_ to to the bridges datacontext, which is (as I said before) a static resource.
Then you simply this to the contextmenu's datacontext:
DataContext="{Binding
Source={StaticResource DataContextBridge},
Path=DataContext}"
Now throw away all the relative pathing etc and use regular binding inside the menu items, and you should be fine. The datacontext will update as usual.
Just one note:
You will obviously have to have some property in the datacontext to discern which command to use, but i'm sure you can figure it out. This solution just deals with the way contextmenu's dont update
Related
I have a binding error that makes no sense. It always works in Release Mode. It sometimes -- only sometimes -- fails to find the source via FindAncestor in Debug Mode
Its for Style for a WPF Path that I use only when that Path is inside a specific custom control named LayerView. Below is the style. Note the 3 bindings that look for the parent source object LayerView are the ones that sometimes fail
<Style x:Key="LayerViewGuidePathStyle" TargetType="{x:Type Path}">
<Setter Property="Data">
<Setter.Value>
<MultiBinding Converter="{StaticResource CvtGuideOption}">
<Binding Source="{svc:ViewSettings}, Path=GuideOption}" />
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ctrl:LayerView}}" Path="ScanWidth" Converter="{ctrl:LogValueConverter}"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ctrl:LayerView}}" Path="ScanHeight" Converter="{ctrl:LogValueConverter}"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ctrl:LayerView}}" Path="SceneTransform" Converter="{ctrl:LogValueConverter}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
When they fail in Debug mode, this is the warning
System.Windows.Data Warning: 4 : Cannot find source for binding with
reference 'RelativeSource FindAncestor,
AncestorType='MyControls.LayerView', AncestorLevel='1''.
BindingExpression:Path=ScanHeight; DataItem=null; target element is
'Path' (Name='GuidePath'); target property is 'Data' (type 'Geometry')
Here is how I use this style inside my custom control LayerView
<ctrl:LayerView x:Name="MainLayerView" ItemsSource="{Binding Shapes}">
<ctrl:LayerView.Layers>
<Path x:Name="GuidePath" Style="{StaticResource LayerViewGuidePathStyle}" />
</ctrl:LayerView.Layers>
</ctrl:LayerView>
As you can probably tell, LayerView is an ItemsControl: Specifically a MultiSelector. My custom version merely adds non-dependency property named Layers which is a Collection<object>. The idea is that the user will set these statically in XAML, as I did above, and I will display them over the standard items of the control.
public class LayerView : MultiSelector
{
static LayerView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LayerView), new FrameworkPropertyMetadata(typeof(LayerView)));
}
private Collection<object>? _layers;
public Collection<object> Layers => _layers ??= new Collection<object>();
}
Finally, here is the ControlTemplate for LayerView. In addition to the required ItemsPresenter, I add an ItemsControl to show the contents of Layers as basically non-hit-detectable "overlays"
<ControlTemplate x:Key="LayerViewTemplate" TargetType="{x:Type gcl:LayerView}">
<Canvas x:Name="PART_MainCanvas" Background="Transparent">
<!-- We are an ItemsControl so present our items -->
<ItemsPresenter x:Name="PART_Items" />
<!-- Now present our custom layers. -->
<ItemsControl x:Name="PART_Layers"
ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Layers}"
IsHitTestVisible="False"/>
</Canvas>
</ControlTemplate
I have no moves here. I can usually fix bindings that fail, but I would expect consistent failure or success. Not a mix. And especially not intermittent failures It's almost as if it's a race condition.
Can anyone point me in the right direction as to why this would fail? Is my Layers property of the wrong type or is it missing some crucial attribute or something?
Without reproducing this error, of course, it is difficult to say exactly what the cause is. But I have some guesses.
The elements of the Layers collection are initialized BEFORE being added to the collection, but the binding is resolved after the element is loaded;
Layers is a regular CLR collection and information about loading ItemsControl (LayerView ) is not passed to it. Therefore, the elements of this collection, as I think, are initialized completely at once, including bindings. And this can happen before these items are included in the ItemsControl panel.
As I think, these errors also occur in Release, it's just that not all errors are caught in this mode. It is guaranteed that only errors that are critical for the operation of the application are caught. For example, exceptions.
At a minimum, you need to replace the collection with a FreezableCollection and implement a read-only DependecyProperty.
True, in this case, you can only include DependencyObject in it.
private static readonly DependencyPropertyKey LayersPropertyKey
= DependencyProperty.RegisterReadOnly(
nameof(Layers),
typeof(FreezableCollection<DependencyObject>),
typeof(LayerView),
new PropertyMetadata(null, (d, e) => ((LayerView)d).protectedLayers = (FreezableCollection<DependencyObject>)e.NewValue));
public static readonly DependencyProperty LayersProperty
= LayersPropertyKey.DependencyProperty;
public FreezableCollection<DependencyObject> Layers
{
get => (FreezableCollection<DependencyObject>)GetValue(LayersProperty);
}
// An optional auxiliary property for quickly obtaining a collection
// in the internal logic of the LayerView and its derivatives.
protected FreezableCollection<DependencyObject> protectedLayers {get; private set;}
// Initializing a Collection in a Constructor.
public LayerView() => SetValue(LayersPropertyKey, new FreezableCollection<DependencyObject>());
Supplement
Unfortunately, I could not immediately check your version - there was no "at hand" computer with the Studio.
Now I checked it.
I did not change the code of the LayerView element itself, I did not set the template in the style.
I got the Binding error only if the Element Template is not set.
<Window x:Class="Core2023.SO.Joe.BindingErrors.BindingErrorsWindow"
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:Core2023.SO.Joe.BindingErrors"
mc:Ignorable="d"
Title="BindingErrorsWindow" Height="150" Width="400">
<Window.Resources>
<ControlTemplate x:Key="LayerViewTemplate" TargetType="{x:Type local:LayerView}">
<ItemsControl x:Name="PART_Layers"
ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Layers}"/>
</ControlTemplate>
</Window.Resources>
<Grid>
<local:LayerView>
<local:LayerView.Layers>
<TextBlock Text="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType={x:Type local:LayerView}}}"/>
</local:LayerView.Layers>
</local:LayerView>
</Grid>
</Window>
In order to accurately diagnose the cause of your problem, I need more complete code to reproduce the problem.
I am running into an issue that I haven't been able to resolve and although I've looked at a few similar posts, I haven't found anything that explains my situation.
Basically I have a WPF Window:
<Window x:Class="NewGame">
<DataContext="{Binding RelativeSource={RelativeSource Self}}"/>
In the class I implement INotifyPropertyChanged to utilize bindings for some properties I have set up to update dynamically using XAML. For instance, I have a DB that has Primary, Secondary and Trim Colors(Hex codes) listed for teams, and the properties automatically will update based on changing the team. So I have the BorderBrush, Foreground and Backgrounds on various things auto-updating in XAML using:
<Foreground="{Binding Path=MyPrimColor}">
<Background={Binding Path=MySecColor}">
<BorderBrush={Binding Path=MyTrimColor}">
, etc...each could be any of the properties, it doesn't matter, those are all working fine.
Now, I have a DataGrid which I need to bind to a DataTable to display the players on the team, and that is where I have run into the issue. It tells me the "Items Collection must be empty before using Itemsource" and throws an exception. This was never an issue until I started using the databindings in XAML, when I had things set in code behind, everything worked fine, but I also know this isn't the way things are supposed to be done, which is why I want to have it working with the XAML data-bindings.
I created MyDT a property as DataTable, and when I try to bind
<DataGrid DataContext="{Binding Path=MyDT}">, it causes the Foreground and Background binding paths to try to bind to the Data.DataTable object as well which obviously throws an error.
I have seen some say I need to use <DataGrid.DataContext> inside the <datagrid> but I haven't gotten that to work either. I understand where the problem is coming from---I already have the bindings set at a higher level, but I just don't know how to fix it in XAML by only binding the DataGrid to the DT property while leaving the others to bind to the class level.
Here is the full code section in XAML:
<DataGrid x:Name="TeamRosterDT"
Height="400"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Foreground="{Binding Path=MyTrimColor}"
RowBackground="{Binding Path=MySecColor}"
AlternatingRowBackground="{Binding Path=MyPrimColor}"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeColumns="False"
ColumnWidth="Auto"
HorizontalScrollBarVisibility="Visible"
ItemsSource="{Binding}"
Opacity="0.8"
VerticalScrollBarVisibility="Visible"
Visibility="Hidden"
DataContext="{Binding Path=MyDT}">
<DataGridColumnHeader Style="{StaticResource DataGridHeaderStyle}" />
<DataGridCell HorizontalContentAlignment="Center" />
</DataGrid>
no need to change DataContext (DataContext="{Binding Path=MyDT}"), only bind ItemsSource (<DataGrid ItemsSource="{Binding Path=MyDT}"> or <DataGrid ItemsSource="{Binding Path=MyDT.DefaultView}">)
Exception is thrown because of incorrect items declaration (lines with DataGridColumnHeader, DataGridCell). They are added to Items list which is not supported when ItemsSource is set
I got the Exceptions resolved(I believe) by using:
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Background" Value="{Binding Path=MyPrimColor}" />
<Setter Property="Foreground" Value="{Binding Path=MyTrimColor}" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<DataGrid.ColumnHeaderStyle>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</DataGrid.CellStyle>
However, the DataTable does not update properly---when the XAML is initialized its an empty DB, which is then filled and filtered according to what team is selected. When a new team is selected, the DT is simply filtered, not refilled. However, since I suppose this technically isn't a "change" to the DT itself, it doesn't fire the OnPropertyChanged event. How can I get it to update properly using XAML triggers, or is than an event I can utilize when the DB refilters?
Is where I would use an ObservableCollection?
I'm fairly new to WPF and am almost positive I've just overlooked something. I'm having issues preserving the state of user controls within my tabs when I select. It seems very similar to this question but it didn't seem to get resolved.
In my main ViewModel I have an ObservableCollection of the abstract TabableViewModel class. It also implements the INotifyPropertyChanged for the TabControl's SelectedIndex.
I want the TabControl's content to be automatically created so I used a DataTemplate like so:
<Window.Resources>
<DataTemplate DataType="{x:Type vm:EasyViewModel}">
<UserControls:EasyControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ComplicatedViewModel}">
<UserControls:ComplicatedControl/>
</DataTemplate>
<DataTemplate x:Key="TabHeaderTemplate">
<Grid>
<TextBlock Text="{Binding HeaderText}" />
</Grid>
</DataTemplate>
<Style x:Key="TabItemStyle" TargetType="TabItem">
<Setter Property="Header" Value="{Binding}" />
<Setter Property="HeaderTemplate" Value="{StaticResource TabHeaderTemplate}" />
<Setter Property="Content" Value="{Binding}" />
</Style>
</Window.Resources>
<TabControl x:Name="contentTab" TabStripPlacement="Bottom"
SelectedIndex="{Binding SelectedTabIndex, Mode=TwoWay}"
ItemsSource="{Binding TabItems, Mode=TwoWay}"
ItemContainerStyle="{StaticResource TabItemStyle}">
</TabControl>
EasyControl and ComplicatedControl both have a number of text boxes, combo boxes and other fields. Both ViewModels extend the TabableViewModel class and implement INotifyPropertyChanged.
In a typical test the ObservableCollection will contain one instance of EasyViewModel and then two instances of ComplicatedViewModel. The tabs build themselves properly, but the state of the ViewModels is not being maintained.
If I make changes inside EasyControl, switch to the first ComplicatedControl tab and then switch back, all data has been lost.
If I make changes to the first ComplicatedControl and switch to the second, I get those same changes instead of a blank slate. If I then switch to EasyControl and back again, all data is once again cleared.
I've seen quite a few examples where the DataTemplates are basic, but I haven't seen any where UserControls are picked based on ViewModel type. Is there anything special I have to do to maintain state?
I don't want to break the MVVM pattern by creating the UIElements within the ViewModels. I'm also hoping that I don't have to extend any Tab controls to get this working, but I will if I have to. I'm surprised it's this hard to do.
So you have a situation where your UI seems to be losing data. This is generally not possible when using MVVM because your data is in your view model and not the view. Therefore, if it is being reset at any point, then it is your code that is resetting it. You said:
My problem comes from switching between tabs
So at the point when you switch tabs, you must initialise one of your view models and that's why it loses its data. You're the only one that can fix this problem... it is not reproducible from your code example. Search for calls to your view model constructor and you should find your problem.
I have a datagrid where in one of the column's header I would like to have a dropdown that filters the data in the grid. The issue being that the datacontext that has the values that would be in this dropdown is in the usercontrol's viewmodel not the datagrids itemssource so the list doesn't seem to be available to the dropdown.
<sdk:DataGridTemplateColumn.HeaderStyle>
<Style TargetType="sdk:DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Assignee" />
<ComboBox x:Name="cboAttorneyHdr" ItemsSource="{Binding Path=Attorneys}"
Margin="3,0,0,0" SelectedItem="{Binding Path=SelectedAttorney, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</sdk:DataGridTemplateColumn.HeaderStyle>
I found an example using relative source for WPF that seems to be asking the same thing but it seems that this doesn't work for Silverlight. I have tried setting this manually in the code behind but the combobox does appear to be available there either!
One way I've found around this problem is to use some helpers as detailed here - it's just one of the possible implementations, but it amounts to emulating the WPF RelativeSourceBinding with AncestorLevel/AncestorType which is still not available in SL4. Or you could try to google 'silverlight combobox in datagrid' for more ways to solve it, I'm sure you can imagine it's a pretty common problem :)
I found this solution which actually ended up working great though it's going to take me a bit to actually understand what the heck it's really doing.
http://weblogs.asp.net/dwahlin/archive/2009/08/20/creating-a-silverlight-datacontext-proxy-to-simplify-data-binding-in-nested-controls.aspx
I am attempting to create a reusable navigation style Custom Control in WPF, like a navigation bar on a website. It will contain links to all the main Pages in my app. This control can go on top of all my Pages in my NavigationWindow. Giving a nice consistent look and feel across pages, like a website.
My issue is in styling the current page's link differently than the other pages' links, so that you can quickly glance at it and see which page you're on. Since the control is the same on each Page, I need to tell it which page is "active" and have it style that link appropriately.
My first thought was to simply place Is<Page>Active properties on the control, one for each page, and then set the appropriate property to true on the page. (Or I could use one property that accepts an Enum value instead of having many properties, either way)
Example:
<local:Header IsHomePageActive="True" />
Now in the control template for my Header Custom Control, I can create a DataTrigger that watches this property:
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource FindAncestor, AncestorType={x:Type local:Header}}, Path=IsHomePageActive}" Value="true">
<Setter ... />
<Setter ... />
<Setter ... />
</DataTrigger>
</Style>
After all that background, here's my question. This works, but I'm going to have to duplicate that DataTrigger, and all the Setters in it, for every single Page I have, because the Trigger has to directly reference the "IsHomePageActive" property which only applies to the one link. So I need a different Style for every link, even though the actual STYLE its describing is exactly the same (by which I mean, the Setters are the same). The only difference is what property the trigger is watching.
Is there some way to do this (or something with the same end result) without ending up with hundreds of lines of duplicated XAML?
How about using a master/detail pattern () with a listbox(say) as the master and your pages as the details.
Then specify your style for the selected item in the list.
The page will change when a different list item is selected and it will look different to the other items.
if you have a dependency property such as List Pages where Page inherits from UserControl and has a string Title you can use
<ListBox
ItemsSource="{Binding Pages}"
IsSynchronizedWithCurrentItem="true">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ScrollViewer VerticalScrollBarVisibility="Auto"
Content="{Binding Pages/}" />
Then just set style for the selected item on the listbox