Cannot hook up the view because of the Data Context (WPF) - c#

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.

Related

Are `Interaction.Behaviors` supposed to wok in WinUI 3 DataTemplated objects?

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);
}
}
}

WPF window not updating with binding in XAML

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";
}

Using Icons in WPF Database Driven Application Results

I am attempting to make a WPF application. The application needs to use a "list view" to show results of queries to the database. I have been able to successfully create the application (GUI, database, LINQ, etc.), however, the display of my query results appear more "gridlike".
The specifications for the project below show that each record that appears in the results needs to have a green circle icon next to it. I have removed the actual results from the images below to keep the contents of the database private.
I don't have enough Reputation Points to post images, so I posted pictures so a sample/testing domain that I use. You can see screenshots here of the WPF app and code here:
http://digitalworkzone.com/WPF.html
What am I doing incorrectly? Is there something I need to add or modify to my code to be able to get the green circles and more of a "list" style to display my query results?
Understand the WPF content model. http://msdn.microsoft.com/en-us/library/bb613548.aspx
Anything that has a 'Content' property basically behaves in two ways. If the 'Content' is set to something that derives from UIElement, then the class will manage it's own presentation. Anything else, however, will just get .ToString() called, and it's text displayed instead.
What this means in the long run is that everything in WPF can display anything. If you want to show a button in a button, you can. For example:
<Button>
<Button.Content>
<Button Content="This will show as text" />
</Button.Content>
</Button>
The inner button will have text, but the outer button will show a Button because Button derives from UIElement and therefore will handle its own presentation.
In your picture examples above, you have ListBoxes/DataGrids that you want to fill in with graphical information. Try this out:
<ListBox HorizontalContentAlignment="Stretch">
<ListBox.Items>
<Button Content="One"/>
<Button Content="Two"/>
<Button Content="Three"/>
<Button Content="Four"/>
</ListBox.Items>
</ListBox>
Now you have a ListBox that shows Buttons instead of Text. You can take this a step further and contain the items in a stackpanel, for example:
<ListBox HorizontalContentAlignment="Stretch">
<ListBox.Items>
<StackPanel Orientation="Horizontal">
<Button Content="A button"/>
<Label Content="Some text" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="A button"/>
<Label Content="Some text" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="A button"/>
<Label Content="Some text" />
</StackPanel>
</ListBox.Items>
</ListBox>
Now we have items that contain a layout container (StackPanels, which then contains other elements).
However, if you set the ItemsSource elsewhere, you can actually use a DataTemplate to display the contents. A DataTemplate in effect targets a particular class and lays out it's contents as defined in XAML. Consider:
Code Behind:
public partial class MyWindow : UserControl {
public MyWindow() {
InitializeComponent();
MyListBox.ItemsSource = new List<Person> {
new Person("Sam", "Smith"),
new Person("Jim", "Henson"),
new Person("Betty", "White"),
};
}
XAML:
<ListBox HorizontalContentAlignment="Stretch" x:Name="MyListBox" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Label Content="{Binding FirstName}"/>
<Label Content="{Binding LastName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now when the Listbox displays, it will cycle through each of the items in the ItemsSource property, and then lay them out using the DataTemplate. It's possible to have the DataTemplate target specific classes by using the DataType property if you're using polymorphism (as in different types of people such as 'Cusomters' or 'Employees' which all derive from 'Person).
The problem with this approach is that you are setting the value of the items directly, which is bad form. It's better to define a class that handles all of the data for your view separately. Consider:
public class ViewModel {
// WPF will automatically read these properties using reflection.
public List<Person> People {
get {
return new List<Person> {
new Person("Sam", "Smith"),
new Person("Jim", "Henson"),
new Person("Betty", "White")
};
}
}
}
That will hold all the data for the view, now let's add it to the actual window. First we need to reference the namespace ('xmlns' means xml namespace):
<Window x:Class="Sharp.MyWindow"
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:lol="clr-namespace:Sharp">
The namespace is Sharp (the namespace where my stuff lives), and the alias we'll give it is lol. Now we attach our ViewModel class to the window by setting it to the DataContext property, as in:
<Window>
<Window.DataContext>
<lol:ViewModel />
</Window.DataContext>
</Window>
This makes all of the public properties on the ViewModel class available to the Window. This way, if we want to read the Persons information into our ListBox, we simply say:
<ListBox HorizontalContentAlignment="Stretch" ItemsSource="{Binding People}" >
...
</ListBox>
Notice that we say ItemsSource={Binding People}, which means 'scan the ViewModel for any public properties called 'People' and then retrieve those results. This is essentially the fundamentals behind the MVVM approach. You might have all of your business logic in one or many classes which handle the main application operation in a Model, but then you have a ViewModel which interacts with the Model and exposes the results as public properties. WPF automatically binds to those properties and presents them for your. The information just flows, rather than setting the values by force.
To really understand how WPF is supposed to work, you should take some time to understand the basics of MVVM. WPF was really designed with MVVM in mind, and so to really get how WPF is supposed to work, you really should take the time to get your head around it. Take a look at:
http://agilewarrior.wordpress.com/2011/01/11/simple-mvvm-walkthrough-part-i/ .
<ListBox ItemsSource="{Binding QueryResults}">
<ListBox.ItemsTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImageSource}"/>
<TextBlock Text="{Binding TextSource}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemsTemplate>
</ListBox>
Will work if you have a list of objects named QueryResults in your code behind. Each object needs to have an string property named ImageSource and a string property named TextSource.
However, since you only need to display a green circle icon for each of the items, you can hardcode the image source. The above will work if you want to have a different icon for each, though.
Also note that in order for this to work, you need to set the DataContext of the window to DataContext="{Binding RelativeSource={RelativeSource Self}}"

Bind button in DataTemplate to command in the form's ViewModel

My problem is similar to the one described in this question:
WPF MVVM Button Control Binding in DataTemplate
Here is my XAML:
<Window x:Class="MissileSharp.Launcher.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MissileSharp Launcher" Height="350" Width="525">
<Grid>
<!-- when I put the button here (outside the list), the binding works -->
<!--<Button Content="test" Command="{Binding Path=FireCommand}" />-->
<ListBox ItemsSource="{Binding CommandSets}">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- I need the button here (inside the list), and here the binding does NOT work -->
<Button Content="{Binding}" Command="{Binding Path=FireCommand}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
It's just a ListBox, bound to an ObservableCollection<string> named CommandSets (which is in the ViewModel).
This binding works (it displays a button for each item in the collection).
Now I want to bind the button to a command (FireCommand), which is also in the ViewModel.
Here's the relevant part of the ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
public ICommand FireCommand { get; set; }
public ObservableCollection<string> CommandSets { get; set; }
public MainWindowViewModel()
{
this.FireCommand = new RelayCommand(new Action<object>(this.FireMissile));
}
private void FireMissile(Object obj)
{
System.Windows.MessageBox.Show("fire");
}
}
The binding of this button does NOT work.
From what I've understood from the question I linked above, the binding doesn't work because:
(correct me if I'm wrong)
The button is inside the ListBox, so it only "knows" the binding of the ListBox (the ObservableCollection, in this case), but not the binding of the main window
I'm trying to bind to a command in the main ViewModel of the main window (which the button doesn't "know")
The command itself is definitely correct, because when I put the button outside the ListBox (see the XAML above for an example), the binding works and the command is executed.
Apparently, I "just" need to tell the button to bind to the main ViewModel of the form.
But I'm not able to figure out the right XAML syntax.
I tried several approaches that I found after some googling, but none of them worked for me:
<Button Content="{Binding}" Command="{Binding RelativeSource={RelativeSource Window}, Path=DataContext.FireCommand}" />
<Button Content="{Binding}" Command="{Binding Path=FireCommand, Source={StaticResource MainWindow}}" />
<Button Content="{Binding}" Command="{Binding Path=FireCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
Could someone please:
give me the proper XAML to bind the button inside the ListBox to a command in the form's MainViewModel?
point me to a link where this advanced binding stuff is explained in a way that a WPF/MVVM beginner can understand?
I'm feeling like I'm just copying and pasting arcane XAML incantations, and so far I don't have any clue (and can't find any good documentation) how I would figure out by myself in which cases I'd need RelativeSource or StaticResource or whatever instead of a "normal" binding.
It's:
{Binding DataContext.FireCommand,
RelativeSource={RelativeSource AncestorType=ListBox}}
No need to walk up to the root unless you actually change the DataContext along the way, but as the ListBox seems to bind to a property on the main VM this should be enough.
The only thing i recommend reading is the Data Binding Overview, and the Binding class documentation (including its properties).
Also here is a short explanation on how bindings are constructed: A binding consists of a source and a Path relative to that source, by default the source is the current DataContext. Sources that can be set explicitly are: Source, ElementName & RelativeSource. Setting any of those will override the DataContext as source.
So if you use a source like RelativeSource and want to access something in the DataContext on that level the DataContext needs to appear in the Path.
This may be considered unrelated by most, but this search is only 1 of 3 results that you'll find searching for data binding commands to controls inside a data template--as it relates to Xamarin Forms. So, maybe it'll help someone now-a-days.
Like me you may wonder how to bind commands inside a BindableLayout. Credit jesulink2514 for answering this at Xamarin Forums, where it's probably overlooked by many because of all the comments. Here's his solution, but I'm including the link below:
<ContenPage x:Name="MainPage">
<ListView Grid.Row="1"
ItemsSource="{Binding Customers}"
VerticalOptions="Fill"
x:Name="ListviewCustomer">
<ListView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Property}"/>
<Button Command="{Binding BindingContext.ItemCommand, Source={x:Reference MainPage}}"
CommandParameter="{Binding .}">Click me</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
https://forums.xamarin.com/discussion/comment/217355/#Comment_217355

Listbox bound to ObservableCollection empty

I have the following xaml:
<Window x:Class="Retail_Utilities.Dialogs.AdjustPriceDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ShowInTaskbar="False"
WindowStartupLocation="CenterOwner" Name="Adjust_Price"
Title="Adjust Price" Background="#ee0e1c64" AllowsTransparency="True" WindowStyle="None" Height="330" Width="570" KeyDown="Window_KeyDown" Loaded="Window_Loaded">
<Grid Height="300" Width="550">
<ListBox HorizontalAlignment="Right" Margin="0,110,35,60" Name="lstReasons" Width="120" VerticalAlignment="Stretch"
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window, AncestorLevel=1}, Path=reasons}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=POS_Price_Change_Reason}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Here is the relevant c#:
namespace Retail_Utilities.Dialogs
{
public partial class AdjustPriceDialog : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Twr_POS_Price_Change_Reason> reasons; ...
and finally, here is the code from another page that opens this window:
AdjustPriceDialog apd = new AdjustPriceDialog();
apd.Owner = (Window)this.Parent;
apd.reasons = new ObservableCollection<Twr_POS_Price_Change_Reason>();
var pcr = from pc in ctx.Twr_POS_Price_Change_Reasons where pc.Deactivated_On == null select pc;
foreach (Twr_POS_Price_Change_Reason pc in pcr)
{
apd.reasons.Add(pc);
}
apd.AdjustingDetail = (Twr_POS_Invoice_Detail)lstDetails.SelectedItem;
if (apd.ShowDialog() == true)
{
}
When the dialog box opens, my lstReasons list is empty. I don't get any errors and when I place a stop in the code, I see that the reasons collection gets populated with the items from the table.
Reasons needs to be a Property (add { get; set;} ). Also, look at Visual Studio Output - it shows Binding errors, there should be some info about failed binding to reasons.
The problem seems to be How you are creating the property.
I know you put your prperty as an observable collection but this doesn't mean it is by it self observalble!
so you need to notify the UI when this property is changed by doing something in the setter like this:
public ObservableCollection<Twr_POS_Price_Change_Reason> reasons
{
get{....}
set
{
Notify('reasons')
}
}
I don't remember the exact code because I didn't use WPF for a while but it is a method in INotifyPropertyChanged, good luck!
It seems your binding path is set to POS_Price_Change_Reason, while the name of your property is reasons. Unless you didn't include POS_Price_Change_Reason in your example code and reasons is the backing field for this property.
Also, keep in mind that you can only bind to public properties, not fields. Additionally, if you change the value of the property, you need to notify the view of this change, by invoking your PropertyChangedEventHandler event for that property:
PropertyChanged(new PropertyChangedEventArgs("YourPropertyName"));

Categories