On Uno Platform project, using several data templates for TabViewItem, TreeViewItem and ListViewItem, trying to fire commands via various events, like ItemInvoked (TreeView), DoubleTapped (ListView), CloseRequested (TabView).
Strangely enough it works for few invocations, then it stops. Note that (also the same) commands bound to Buttons via their Command binding continue working.
Example of TabView close attempt. Typically works for first 3 to 5 tabs:
<DataTemplate x:Key="HtmlFileTemplate" x:DataType="local:FileContentViewModel">
<TabViewItem Header="{x:Bind Info.Name}">
<StackPanel Orientation="Vertical">
<TextBlock Text="{x:Bind Content}" />
<!--
This just works:
-->
<Button Command="{x:Bind CloseCommand}">Invoke FileContentViewModel.CloseCommand</Button>
</StackPanel>
<!--
This stops working after few invocations
(typically together with all other Interaction.Behaviors bindings):
-->
<i:Interaction.Behaviors>
<ic:EventTriggerBehavior EventName="CloseRequested">
<ic:InvokeCommandAction Command="{x:Bind CloseCommand}" />
</ic:EventTriggerBehavior>
</i:Interaction.Behaviors>
</TabViewItem>
</DataTemplate>
<local:TabsTemplateSelector x:Key="TabsTemplateSelector" HtmlFileTemplate="{StaticResource HtmlFileTemplate}" ... />
...
<TabView TabItemsSource="{x:Bind ViewModel.Tabs}" TabItemTemplateSelector="{StaticResource TabsTemplateSelector}">
</TabView>
I hope, there is some flagrant issue in my usage I can't simply see. Any Help appreciated. Using latest uno stuff (WinUI, dotnet6), debugging on Windows head:
dotnet new unoapp -o UnoWinUI3AppName
<PackageReference Include="CommunityToolkit.Mvvm" Version="7.1.2" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.3" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.1" />
<PackageReference Include="Uno.Microsoft.Xaml.Behaviors.Interactivity.WinUI" Version="2.3.0" />
<PackageReference Include="Uno.Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.3.0" />
Still trying to find minimal sample exhibiting the mentioned issues.
Also tried to check the XAML generated code, but it is too much... well... generated :-(
EDIT:
"Simplified" the use case to following:
Have TabView on a page.
First tab contains ListView with items.
Other tabs contain "opened" items (dummy record).
Item opens on double-click (DoubleTapped) in ListView on first page.
Item closes on X click on a tab (in TabView).
Both TabView and ListView use DataTemplates.
Separate UI from code as much as possible (binding, commands, VMs, etc.).
Based on default WinUI "Hello World" app template.
Project files
EDIT 2:
For anyone interested, based on Andrew KeepCoding's hints and linked issue answers, I mixed code behind with custom attaching of the behaviors to the templated items via new attached property:
public static class InteractionEx
{
public static readonly DependencyProperty AttachBehaviorsProperty = DependencyProperty.RegisterAttached
(
"AttachBehaviors",
typeof(object),
typeof(FrameworkElement),
new PropertyMetadata(false, AttachBehaviorsChanged)
);
public static object GetAttachBehaviors(DependencyObject o) => o.GetValue(AttachBehaviorsProperty);
public static void SetAttachBehaviors(DependencyObject o, object value) => o.SetValue(AttachBehaviorsProperty, value);
private static void AttachBehaviorsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behaviors = e.NewValue switch
{
Behavior single => new BehaviorCollection { single },
BehaviorCollection multiple => multiple,
_ => null,
};
Interaction.SetBehaviors(d, behaviors);
}
}
In XAML the behaviors then can be attached to a control like:
<ListView ItemsSource="{x:Bind Items}" ItemTemplateSelector="{StaticResource ListItemTemplateSelector}" SelectedItem="{x:Bind SelectedItem, Mode=TwoWay}">
<local:InteractionEx.AttachBehaviors>
<interactivity:BehaviorCollection>
<core:EventTriggerBehavior EventName="DoubleTapped">
<core:InvokeCommandAction Command="{x:Bind OpenCommand}" />
<core:CallMethodAction MethodName="Open" TargetObject="{x:Bind}" />
</core:EventTriggerBehavior>
</interactivity:BehaviorCollection>
</local:InteractionEx.AttachBehaviors>
</ListView>
I think you'll find these answers helpful.
But let me suggest another option. I have spent some time with your repro project and in my opinion, I think it'd be cleaner and readable if you drop Interaction.Behaviors. Code-behind is not evil if it's UI related and no business logic there.
MainWindow.xaml
<Window
x:Class="TabViewTest.MainWindow"
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:helpers="using:TabViewTest.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodels="using:TabViewTest.ViewModels"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="BrowserTemplate" x:DataType="viewmodels:BrowserTabViewModel">
<TabViewItem Header="Browser">
<ListView x:Name="ItemList" ItemsSource="{x:Bind Items}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewmodels:ItemViewModel">
<TextBlock DoubleTapped="TextBlock_DoubleTapped" Text="{x:Bind Id}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</TabViewItem>
</DataTemplate>
<DataTemplate x:Key="ContentTemplate" x:DataType="viewmodels:ContentTabViewModel">
<TabViewItem Header="{x:Bind ItemViewModel.Id}">
<TextBlock Text="{x:Bind ItemViewModel.Id}" />
</TabViewItem>
</DataTemplate>
<helpers:TabItemTemplateSelector
x:Key="TabTemplateSelector"
BrowserTemplate="{StaticResource BrowserTemplate}"
ContentTemplate="{StaticResource ContentTemplate}" />
</Grid.Resources>
<TabView
TabCloseRequested="TabView_TabCloseRequested"
TabItemTemplateSelector="{StaticResource TabTemplateSelector}"
TabItemsSource="{x:Bind ViewModel.Tabs}" />
</Grid>
</Window>
MainWindow.xaml.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using System.Linq;
namespace TabViewTest.ViewModels;
[ObservableObject]
public partial class MainWindowViewModel
{
[ObservableProperty]
private ObservableCollection<TabViewModel> tabs = new();
private readonly BrowserTabViewModel browserTabViewModel;
public MainWindowViewModel()
{
browserTabViewModel = new();
Tabs.Add(browserTabViewModel);
}
[RelayCommand]
private void NewTabRequest(ItemViewModel itemViewModel)
{
Tabs.Add(new ContentTabViewModel(itemViewModel));
}
[RelayCommand]
private void CloseTabRequest(ItemViewModel itemViewModel)
{
if (Tabs
.OfType<ContentTabViewModel>()
.Where(x => x.ItemViewModel == itemViewModel)
.FirstOrDefault() is ContentTabViewModel target)
{
Tabs.Remove(target);
}
}
}
Related
DISCLAIMER
This issue is only reported in the XAML Designer itself, when I run the application in debug mode, I can't see any XAML Binding issues at a runtime, and the ContentPresenter works as expected.
DataTemplateSelector:
internal sealed class BooleanDataTemplateSelector : DataTemplateSelector
{
#region Public Properties
public DataTemplate? FalseDataTemplate { get; set; }
public DataTemplate? TrueDataTemplate { get; set; }
#endregion Public Properties
#region Public Methods
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (FalseDataTemplate == null || TrueDataTemplate == null) return new DataTemplate();
var isLoading = (bool)item;
return isLoading ? TrueDataTemplate : FalseDataTemplate;
}
#endregion Public Methods
}
ResourceDictionary which holds the template and selector with x:Key:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mat="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:pages="clr-namespace:ComatiQ.Configurator.Client.Core.ViewModels.Pages;assembly=ComatiQ.Configurator.Client.Core"
xmlns:resx="clr-namespace:ComatiQ.Configurator.Client.Wpf.Strings.Pages.HomePage"
xmlns:selectors="clr-namespace:ComatiQ.Configurator.Client.Wpf.Selectors">
<DataTemplate
x:Key="ContentLoadingTemplate"
DataType="{x:Type pages:HomePageViewModel}">
<mat:Card>
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Vertical">
<TextBlock
Margin="0,0,0,10"
Style="{StaticResource MaterialDesignHeadline6TextBlock}"
Text="{x:Static resx:HomePage.RouteOperation_Loading}" />
<ProgressBar
Height="20"
IsIndeterminate="True"
Style="{StaticResource MaterialDesignLinearProgressBar}" />
</StackPanel>
</mat:Card>
</DataTemplate>
<DataTemplate
x:Key="NullTemplate" />
<selectors:BooleanDataTemplateSelector
x:Key="LoadingTemplateSelector"
FalseDataTemplate="{StaticResource NullTemplate}"
TrueDataTemplate="{StaticResource ContentLoadingTemplate}" />
</ResourceDictionary>
HomePageView.xaml part of the code which reports an issue:
<Grid
Grid.Row="1"
Grid.Column="1"
Margin="2.5"
Panel.ZIndex="1">
<!-- CONTENT VIEWER -->
<ContentPresenter
Panel.ZIndex="0"
Content="{Binding DisplayedViewModel}"
ContentTemplateSelector="{StaticResource ViewContentTemplateSelector}" />
<!-- CONTENT LOADING INFO -->
<ContentPresenter
Panel.ZIndex="1"
Content="{Binding IsRouteLoading}"
ContentTemplateSelector="{StaticResource LoadingTemplateSelector}" />
</Grid>
The ContentPresenter under the reports:
**Severity Code Description Project File Line Suppression State
Error XDG0066 Object reference not set to an instance of an object. ComatiQ.Configurator.Client.Wpf D:\Programming\Projects\DomatiQ DALI Configurator\DomatiQ DALI Configurator\ComatiQ.Configurator.Client.Wpf\Views\Pages\HomePageView.xaml 138
**
I created a lot of software solutions for my company, which are WPF based, yet I never encountered this particular issue. Is this a Visual Studio 2022 XAML Designer Bug, because otherwise I've no idea why I get any errors.
Turns out it is a Visual Studio 2022 XAML Designer bug.
I'm now using Rider which I prefer anyway, and use VS2022 just to apply formatting when creating PR's.
So my problem is that, initially I got the following code in my MainWindow.xaml class
<Button Margin="10,20,10,10" Height="37" Command="{Binding AddCmd}">View all customers</Button>
....
<TabControl x:Name="myTabs" ItemsSource="{Binding WorkSpaces}" Height="323">
<TabControl.Resources>
<DataTemplate DataType="{x:Type viewmodel:AllCustomersViewModel}">
<views:AllCustomersView/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemTemplate>
....
</TabControl.ItemTemplate>
</TabControl>
and in the MainWindow.xaml.cs class, I have this code
public MainWindow()
{
InitializeComponent();
this.DataContext = new DemoWPFApp.ViewModel.MainWindowViewModel();
myTabs.DataContext = this.DataContext; //that is where the problem is
}
So, basically what I want is that when I click the button "View All Customers" it will add a new AllCustomerViewModel to the list of WorkSpaces, and show the AllCustomerView on the screen.
However, I cannot do this. BUT when I delete the line "myTabs.DataContext = this.DataContext;" in the MainWindow.xaml.cs class, it works !
Can someone explain to me why ? Because I already set my AllCustomerView data context to be AllCustomerViewModel. And I cannot understand why that line is the problem of my application.
Thank you.
I'm using a Messenger class in order to send data between view models. There is an AppView that hosts two main views in a content control, and up until now have had no issue with sending/receiving data this way.
Issue:
Now I added a ProductView that shows a separate dialog to the AppView. But when I call Messenger.Default.Send<ProductModel>(SelectedProduct); after calling .ShowDetailDialog() this blocks the Send code call, until the dialog is closed.
I tried the other way around, calling the Send code first, then opening the dialog. But this means that the message handler in the receiving VM doesn't register in time before the message is sent.
Does anyone know of a solution, to prevent the dialog from blocking the send call? Or alternatively register the ProductVM message handler prior to sending message and showing dialog?
Below is a summary of the related classes:
CustomerOrdersVM (sending code):
private void EditOrder(object obj)
{
_dialogService.ShowDetailDialog();
Messenger.Default.Send<ProductModel>(SelectedProduct);
}
ProductVM (receiving code):
public ProductViewModel()
{
Messenger.Default.Register<ProductModel>(this, OnSelectedProductReceived);
}
DialogService:
class DialogService : IDialogService
{
Window productView = null;
public DialogService()
{
}
public void ShowDetailDialog()
{
productView = new ProductView();
productView.ShowDialog();
}
}
AppVM (Main VM's are registered, ProductVM is independent of this VM):
public ApplicationViewModel()
{
// Add available pages
PageViewModels.Add(new CustomerDetailsViewModel(customerDataService, countryDataService, dialogService));
PageViewModels.Add(new CustomerOrdersViewModel(orderDataService, dialogService));
PageViewModels.Add(new OrderStatisticsViewModel());
// Set starting page
CurrentPageViewModel = PageViewModels[0];
}
AppView: (holds the AppVM views):
<Window x:Class="MongoDBApp.Views.ApplicationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:MongoDBApp.Views"
xmlns:vm="clr-namespace:MongoDBApp.ViewModels">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:CustomerDetailsViewModel}">
<views:CustomerDetailsView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:CustomerOrdersViewModel}">
<views:CustomerOrdersView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:OrderStatisticsViewModel}">
<views:OrderStatisticsView />
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<vm:ApplicationViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=".07*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<TabControl Grid.Row="1"
ItemsSource="{Binding PageViewModels}"
SelectedItem="{Binding CurrentPageViewModel}"
TabStripPlacement="Top">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</Grid>
</Window>
You could solve the problem a few ways:
Don't use ShowDialog(). Use Show(), and make the dialog window TopMost and parented to the main window.
Register the ProductView in the constructor of the DialogService (do you really need a new ProductView each time anyway?)
Make the DialogService (or a utility class inside of it) register at construction time for the message, and then pass the message on to any displayed ProductViews
Personally I like #2- since you are using ShowDialog, it implies that only one ProductView is ever needed at a time. For example:
class DialogService : IDialogService
{
Window productView = null;
ProductView _productView;
public DialogService()
{
_productView = new ProductView();
}
public void ShowDetailDialog()
{
_productView.ShowDialog();
}
}
I have a project with a Window Manager using AvalonDock.
Basically there is two Element : a LayoutAnchorableItem to show my different tool box (currently one, consisting of a Treeview) and a LayoutItem to show the document opened with the treeview (a custom control, with bindable parameters - in theory)
The ViewModel of the DockingManager hosts the ObservableCollection named Panes that will be the LayoutItems.
Things works "fine" if I don't try to bind the parameters in the XAML, and force the values like this
<avalonDock:DockingManager.LayoutItemTemplateSelector>
<panes:PanesTemplateSelector>
<panes:PanesTemplateSelector.ExchangeViewTemplate>
<DataTemplate>
<xchng:Exchange/>
</DataTemplate>
</panes:PanesTemplateSelector.ExchangeViewTemplate>
<panes:PanesTemplateSelector.GraphViewTemplate>
<DataTemplate>
<grph:Graph TickerCode="ILD" ExchangeCode="EPA"/>
</DataTemplate>
</panes:PanesTemplateSelector.GraphViewTemplate>
</panes:PanesTemplateSelector>
</avalonDock:DockingManager.LayoutItemTemplateSelector>
Exchange is the toolbox and Graph is the LayoutItems.
The initial databinding for the docking manager is done like this :
<avalonDock:DockingManager Margin="0,0,0,0"
Grid.Row="1"
AnchorablesSource="{Binding Tools}"
DocumentsSource="{Binding Panes}"
ActiveContent="{Binding ActiveDocument, Mode=TwoWay, Converter={StaticResource ActiveDocumentConverter}}"
x:Name="dockManager">
Note that Pane is of type GraphViewModel which has two public parameters : ExchangeCode and TickerCode.
The thing is I want to bind the TickerCode and ExchangeCode to the Panes.TickerCode and Panes.ExchangeCode values.
So I tried this :
<grph:Graph TickerCode="{Binding TickerCode, UpdateSourceTrigger=PropertyChanged}" ExchangeCode="{Binding ExchangeCode, UpdateSourceTrigger=PropertyChanged}"/>
But it does nothing : TickerCode and ExchangeCode in the custom control are equal to "" contrary to when I force the values in the XAML.
Also the somewhat weird thing is that if I step in the code execution, Panes actually have values for TickerCode and ExchangeCode, they just don't bind. For instance, the code that actually create the pane is
public void AddGraph(string FullName, string ExchangeCode, string TickerCode)
{
var graphViewModel = new GraphViewModel(FullName, ExchangeCode, TickerCode);
_panes.Add(graphViewModel);
ActiveDocument = graphViewModel;
}
Here, every step has both values. And let's imagine that I add 5 different panes, they are all with their correct ExchangeCode and TickerCode, but nothing is passed to the custom control.
If you need more info on my custom control that values are bound to, here is the code : Passing parameters to custom control (databinding).
Remark: As you see I didn't put much of my code, make request if you think it may help and I will add what's needed. Note that the global logic of the whole window manager is the same provided in the AvalonDock test app (AvalonDock.MVVMTestApp).
For example, if I’ve got ChartView and ChartViewModel:
In MainWindow.xaml:
<xcad:DockingManager x:Name="dockingManager"
AnchorablesSource="{Binding Path=Anchorables}"
DocumentsSource="{Binding Path=Documents}"
ActiveContent="{Binding Path=ActiveDocument, Mode=TwoWay, Converter={StaticResource ActiveDocumentConverter}}">
<xcad:DockingManager.LayoutItemTemplateSelector>
<selfViewPane:PaneTemplateSelector>
<selfViewPane:PaneTemplateSelector.ChartViewTemplate>
<DataTemplate>
<selfViewDocument:ChartView />
</DataTemplate>
</selfViewPane:PaneTemplateSelector.ChartViewTemplate>
</selfViewPane:PaneTemplateSelector>
</xcad:DockingManager.LayoutItemTemplateSelector>
<xcad:DockingManager.LayoutItemContainerStyleSelector>
<selfViewPane:PaneStyleSelector>
<selfViewPane:PaneStyleSelector.ChartViewStyle>
<Style TargetType="{x:Type xcad:LayoutItem}">
<Setter Property="Title" Value="{Binding Model.Title}"/>
<Setter Property="CloseCommand" Value="{Binding Model.CloseCommand}"/>
<Setter Property="IconSource" Value="{Binding Model.IconSource}"/>
<Setter Property="ContentId" Value="{Binding Model.ContentId}"/>
</Style>
</selfViewPane:PaneStyleSelector.ChartViewStyle>
</selfViewPane:PaneStyleSelector>
</xcad:DockingManager.LayoutItemContainerStyleSelector>
<xcad:DockingManager.LayoutUpdateStrategy>
<selfViewPane:LayoutInitializer />
</xcad:DockingManager.LayoutUpdateStrategy>
<xcad:LayoutRoot>
<xcad:LayoutPanel Orientation="Horizontal">
<xcad:LayoutAnchorablePane Name="ToolsPane" DockWidth="200">
</xcad:LayoutAnchorablePane>
<xcad:LayoutDocumentPane />
</xcad:LayoutPanel>
</xcad:LayoutRoot>
</xcad:DockingManager>
And:
In ChartViewModel I’ve got property ChartPlotModel:
/// <summary>
/// Gets or sets the ChartPlotModel.
/// </summary>
public PlotModel ChartPlotModel
{
get
{
return this.chartPlotModel;
}
set
{
if (this.chartPlotModel != value)
{
this.chartPlotModel = value;
this.RaisePropertyChanged("ChartPlotModel");
}
}
}
In ChartView I can bind:
<UserControl x:Class="Jofta.Analyzer.UI.Classes.View.Document.ChartView"
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:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:oxy="http://oxyplot.org/wpf"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<xctk:BusyIndicator IsBusy="{Binding Path=IsBusy}">
<Grid>
<oxy:PlotView Model="{Binding ChartPlotModel}" />
</Grid>
</xctk:BusyIndicator>
</UserControl>
In this example I’m binding to PlotView from oxyplot, but I think, you can use this pattern. You’ve got GraphViewModel, GraphView and TickerCode and ExchangeCode.
Can someone please explain what's going on here? I'm new to WPF and migrating my Forms project to WPF with binding. I'm using AvalonDock but I'm not binding directly to any of the AvalonDock controls. Here's a couple excerpts. I removed a lot for brevity's sake but let me know if you need to see something else.
EDIT: These two StackPanels are just tests... trying to figure this stuff out.
EDIT2: I'm trying to do MVVM eventually; I just need to get a better handle on binding so I know how to structure it.
EDIT3: See bottom of post.
Q: The first StackPanel does not update at all, never mind updating after changes. I've tried setting the DataContext in the StackPanel, Grid and TextBlock. What am I doing wrong?
Q: The second works fine when the parent grid is bound in code behind but only if bound where you see it, not in the MainWindow_Loaded() method. What's different here?
I've read several tutorials as well as plenty of similar questions here but nothing's helping me understand what the difference is here and what I'm missing.
<Window x:Class="TestUIWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ad="http://schemas.xceed.com/wpf/xaml/avalondock"
Title="MainWindow" Height="768" Width="1024"
Loaded="MainWindow_Loaded"
xmlns:vm="clr-namespace:TestUIWPF.ViewModel"
>
<!-- lots excluded for brevity. there are no Window.Resources -->
<ad:LayoutAnchorable Title="Test" >
<Grid x:Name="gridTest">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<StackPanel.DataContext>
<vm:EntityViewModel />
</StackPanel.DataContext>
<TextBlock Text="Label" />
<TextBlock DataContext="{Binding ActiveEntity}" Text="{Binding Path=Label}" />
</StackPanel>
<StackPanel Orientation="Horizontal" >
<TextBlock Text="Label Again" />
<TextBlock Text="{Binding Path=Label}" />
</StackPanel>
</StackPanel>
</Grid>
</ad:LayoutAnchorable>
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = this;
SelectedEntityViewModel = new ViewModel.EntityViewModel();
ImportEntityXML_Click(null, null); //skips clicking the menus
}
private void ImportEntityXML_Click(object sender, RoutedEventArgs e)
{
//omitted OpenFileDialog and XmlReader stuff
xmlreader = new XmlReader(dlg.FileName);
Entities.Add(xmlreader.ReadEntityFromXML());
SimulatedEntitySelection(Entities.ElementAt(0)); //haven't built any of the UI stuff for this yet
}
private void SimulatedEntitySelection(Entity ent)
{
SelectedEntityViewModel.ActiveEntity = ent;
gridTest.DataContext = SelectedEntityViewModel.ActiveEntity;
}
private void button_Click(object sender, RoutedEventArgs e)
{
SelectedEntityViewModel.ActiveEntity.Label = "test";
}
Entity and EntityViewModel implement INotifyPropertyChanged and it works just fine with the second StackPanel. The button that calls button_Click() is just for testing the binding. EntityViewModel pretty much just wraps Entity through the ActiveEntity property and helps with reading the collections-of-collections within Entity.
EDIT3:
I've also tried a couple resources. Here's how I did the ObjectDataProvider:
<Window.Resources>
<ObjectDataProvider x:Key="testVM" ObjectType="{x:Type vm:EntityViewModel}" />
<vm:EntityViewModel x:Key="SelEntVM" />
</Window.Resources>
<!-- .... -->
<StackPanel.DataContext>
<Binding Source="{StaticResource testVM}" />
</StackPanel.DataContext>
<TextBlock Text="Label" />
<TextBlock Text="{Binding Path=ActiveEntity.Label}" />
Your first stack panel is not working, because the data context is inherited. Therefore, once you change the DataContext of the Grid to the ActiveEntity object, the binding on the text block in the first data context will set the datacontext for the TextBlock to the ActiveEntity on the current datacontext, which would already be the ActiveEntity (therefore ActiveEntity.ActiveEntity) And than try to bind to the Label property on that. E.g. ActiveEntity.ActiveEntity.Label
Before the click, you are setting the DataContext of that window to "this" which I am assuming is not the ViewModel, it is the code behind?
If you are using MVVM,
you should have something like this
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
SelectedEntityViewModel = new ViewModel.EntityViewModel();
this.DataContext = SelectedEntityViewModel;
ImportEntityXML_Click(null, null); //skips clicking the menus
}
Or some other ViewModel which provides all the necessary data.
You nomrally would have a MainWindowView and MainWindowViewModel, at least that is the convention and usually you set the datacontext of the window in the constructor once(you can do it in the Loaded handler), in most cases you shouldn't need to manually change the DataContext of any framework elements in the code behind.
EDIT: Example Code:
MainWindow.xaml
<Window x:Class="SO27760357.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="300">
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Label" />
<TextBlock Text="{Binding ActiveEntity.Label}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Label Again" />
<TextBlock Text="{Binding ActiveEntity.Label}" />
</StackPanel>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
MainWindowViewModel.cs (INotifyPropertChanged omitted for simplicity)
public class MainWindowViewModel
{
public EntityViewModel ActiveEntity { get; set; }
}
EntityViewModel.cs (INotifyPropertChanged omitted for simplicity)
public class EntityViewModel
{
public string Label { get; set; }
}
As you can see, I am setting the DataContext of the Window to the MainViewModel, therefore the DataContext(root of all bindings) is the MainViewModel, and each TextBlock needs to first access the ActiveEntity property first, before it can get to the Label proeprty.
THe other option is, that if everything inside the main stack panel you given us, will be bound to ActiveEntity, you can change the DataContext of that StackPanel, binding it to the ActiveEntity, and therefore all of its children datacontext will also be that object.
<StackPanel Orientation="Vertical"
**DataContext="{Binding ActiveEntity}"**>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Label" />
<TextBlock **Text="{Binding Label}"**/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Label Again" />
<TextBlock **Text="{Binding Label}"** />
</StackPanel>
</StackPanel>
EDIT 2 - Advice
You should refrain from referencing objects by name as much as possible, and have as little logic in the code behind as possible, if any. For most simple screens there is no need to have anything in code behind other than the initial Binding of the DataContext (if you don't have a window service which creates + sets the DataContext of windows)
It does work. You might be probably updating a wrong viewmodel.
Once you define the viewmodel in DataContext, youd have to access it this way:
private void button_Click(object sender, RoutedEventArgs e)
{
var myModel = (ViewModel.EntityViewModel)(yourStackPanelName.DataContext);
myModel.ActiveEntity.Label = "test";
}