I'm trying to bind a IAsyncRelayCommand (MVVM Toolkit) from my view model using x:Bind within a DataTemplate.
This is my view model:
public class BuildingViewModel : ObservableObject
{
public ObservableCollection<ObservableProjectItem> ProjectItems { get; } = new ObservableCollection<ObservableProjectItem>();
public IAsyncRelayCommand AddProjectItemCommand { get; }
public BuildingViewModel()
{
AddProjectItemCommand = new AsyncRelayCommand<ContentDialog>(async (dialog) => await AddProjectItem(dialog));
}
private async Task AddProjectItem(ContentDialog dialog)
{
// Do something
}
}
And this my view (XAML):
<TreeView ItemsSource="{x:Bind ViewModel.ProjectItems}">
<TreeView.ItemTemplate>
<DataTemplate x:DataType="model:ObservableProjectItem">
<TreeViewItem ItemsSource="{x:Bind Children}">
<TextBlock Text="{x:Bind Name}" />
<TreeViewItem.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem Command="{HERE I WANT BIND THE COMMAND}" />
<MenuFlyoutItem Text="Löschen" Icon="Delete" />
</MenuFlyout>
</TreeViewItem.ContextFlyout>
</TreeViewItem>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The view have the following code behind:
public sealed partial class BuildingView : Page
{
public BuildingView()
{
this.InitializeComponent();
}
internal BuildingViewModel ViewModel = Ioc.Default.GetService<BuildingViewModel>();
}
My problem is {x:Bind ViewModel.AddProjectItemCommand} doesn't work. Within the DataTemplate the x:Bind scope is ObservableProjectItem. How can I navigate to my root ViewModel respectively bind my command declared in the view model?
Unfortunately, these kind of operations are not well supported by x:Bind (see this WinUI issue for more context). You could however use Binding to solve that problem. For this, you need to set x:Name on your page and use the ElementName property:
<Page x:Name="MyPage">
<TreeView ItemsSource="{x:Bind ViewModel.ProjectItems}">
<TreeView.ItemTemplate>
<DataTemplate x:DataType="model:ObservableProjectItem">
<TreeViewItem ItemsSource="{x:Bind Children}">
<TextBlock Text="{x:Bind Name}" />
<TreeViewItem.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem Command="{Binding Path=AddProjectItemCommand, ElementName=MyPage}" />
<MenuFlyoutItem Text="Löschen" Icon="Delete" />
</MenuFlyout>
</TreeViewItem.ContextFlyout>
</TreeViewItem>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Page>
I have also encountered this problem.
My solution is this:
declare ICommand in ViewModel as static
use a construct {x:Bind Path=...}
Something like:
In ViewModel:
static public ICommand Command_TapColorSet { get; set; }
in XAML:
xmlns:models="using:TestProj.Models"
xmlns:viewmodels="using:TestProj.ViewModels"
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="models:ColorsSet">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="Tapped">
<Core:InvokeCommandAction Command="{x:Bind Path=viewmodels:OptionsViewModel.Command_TapColorSet}" CommandParameter="{Binding}" />
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>.
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
I realise that static ICommand looks a bit strange, but at least this approach works.
Related
Here's my problem, I want to have a few ListViews with their ItemsSource attached with binding to SelectedItem of other ListViews.
Here's a picture to show you the interface to give you an overlook :
So I have a "Tours" ListView who's ItemSource is bonded with a ObservableCollection property.
Now I want the "Parties" ListView to be bond with the SelectedItem of the "Tours" ListView.
Afterwards, I want the "Équipes" ListView to be bound with the SelectedItem of the "Parties" ListView.
And so on...
Right now, it is working, but the program crash :
Select a "Tour" from "Tours"
Select a "Partie" from "Parties"
Select a "Équipe" from "Équipes"
Select a "Different "Tour" from "Tours"
Crash
I supposed it was when I change "Tour", the "Équipe" point toward something inexistant.
This is the XAML of the ListViews and the content (the content isn't bond right now) :
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.2*" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<Label Style="{StaticResource menu}" Content="Tours" />
<ListView Name="lvTours" ItemsSource="{Binding Tours}" SelectionChanged="lvTours_SelectionChanged">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Nom}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
<Label Style="{StaticResource menu}" Content="Parties" />
<ListView Name="lvParties" DataContext="{Binding RelativeSource={RelativeSource Self}}" SelectionChanged="lvParties_SelectionChanged" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Nom}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
<Label Style="{StaticResource menu}" Content="Équipes" />
<ListView Name="lvEquipes" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Nom}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
</StackPanel>
<Label Content="Here's the content..." Grid.Column="1" Margin="30" />
</Grid>
And here's the cs :
using Lama.Logic.Model.Test;
using System.Windows.Controls;
namespace Lama.UI.UC.TournoiControls.StatistiquesControls
{
/// <summary>
/// Interaction logic for Statistiques.xaml
/// </summary>
public partial class StatistiquesUC : UserControl
{
public Tour SelectedTour { get; set; }
public Partie SelectedPartie { get; set; }
public StatistiquesUC()
{
InitializeComponent();
}
private void lvTours_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
lvParties.ItemsSource = ((Tour)lvTours.SelectedItem).Parties;
}
private void lvParties_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
lvEquipes.ItemsSource = ((Partie)lvParties.SelectedItem).Equipes;
}
}
}
So :
"Tours" is a property of ObservableCollection
each "Tour" has a property "Parties" of ObservableCollection
each "Partie" has a property "Equipes" of ObservableCollection
and so on...
Thanks and sorry for my english!
Since you dont want a ViewModel, this is an other approche :
In your xaml does not need to SelectionChanged, just bind itemSource of Lisview Equipe & Partie the parent list view like this :
<Grid DataContext="{Binding YourViewModel}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.2*" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<Label Style="{StaticResource menu}" Content="Tours" />
<ListView Name="lvTours" ItemsSource="{Binding Tours}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Nom}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
<Label Style="{StaticResource menu}" Content="Parties" />
<ListView Name="lvParties" ItemsSource="{Binding ElementName=lvTours, Path=SelectedItem.Parties}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Nom}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
<Label Style="{StaticResource menu}" Content="Équipes" />
<ListView Name="lvEquipes" ItemsSource="{Binding ElementName=lvParties, Path=SelectedItem.Equipes}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Nom}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
</StackPanel>
<Label Content="Here's the content..." Grid.Column="1" Margin="30" />
</Grid>
Hope it helps
I seem to have a simple data-binding problem, but can't figure out the right way to do it. There is a TabControl which defines two DataTemplate's, one for the tab header and one for the tab content.
The content template contains an ItemsControl. The ItemsControl tries to bind to a dynamically created ViewModel (ConnectionInfoVM).
When I display the UI, the binding just fails, but there is no error-message in the output about it.
How do I have to set up the DataContext and the binding so the binding works and the DataBuffer is actually displayed? Any help greatly appreciated.
ConnectionsControl:
<UserControl x:Class="XXXViewer.Views.ConnectionsControl"
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:viewModels="clr-namespace:XXXViewer.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TabControl Grid.Row="0" Name="TabDynamic" SelectionChanged="tabDynamic_SelectionChanged">
<TabControl.Resources>
<DataTemplate x:Key="TabHeader" DataType="TabItem">
<DockPanel>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Header}" />
<Button Name="btnDelete" DockPanel.Dock="Right" Margin="5,0,0,0" Padding="0" Click="btnTabDelete_Click" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Name}">
<Image Source="{DynamicResource DeleteImg}" Height="11" Width="11"></Image>
</Button>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="TabContent" DataType="viewModels:ConnectionInfoVM">
<StackPanel>
<ScrollViewer Name="Scroller" Background="Black">
<StackPanel>
<TextBlock Text="This line gets printed" Foreground="White" FontFamily="Consolas"/>
<ItemsControl Name="ItemCtrl" ItemsSource="{Binding DataBuffer}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}" Foreground="White" FontFamily="Consolas"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</StackPanel>
</DataTemplate>
</TabControl.Resources>
</TabControl>
</Grid>
</UserControl>
ConnectionsControl code behind:
namespace XXXViewer.Views
{
public partial class ConnectionsControl : UserControl
{
private readonly ObservableCollection<TabItem> _tabItems = new ObservableCollection<TabItem>();
public ConnectionsControl()
{
InitializeComponent();
// bindings
TabDynamic.ItemsSource = _tabItems;
TabDynamic.DataContext = this;
}
// assume this gets called
private void AddTabItem(ConnectionInfoVM ci)
{
DataTemplate headerTemplate = TabDynamic.FindResource("TabHeader") as DataTemplate;
DataTemplate contentTemplate = TabDynamic.FindResource("TabContent") as DataTemplate;
// create new tab item
TabItem tab = new TabItem
{
Header = $"Tab {ci.ConnectionID}",
Name = $"T{ci.ConnectionID}",
HeaderTemplate = headerTemplate,
ContentTemplate = contentTemplate,
DataContext = ci
};
_tabItems.Insert(0, tab);
// set the new tab as active tab
TabDynamic.SelectedItem = tab;
}
}
}
ConnectionInfoVM:
namespace XXXViewer.ViewModels
{
public class ConnectionInfoVM : ViewModelBase
{
private readonly ObservableQueue<string> _dataBuffer = new ObservableQueue<string>();
public ObservableQueue<string> DataBuffer => _dataBuffer;
}
}
Screenshot of the tab that gets created:
resulting tab
You set the ContentTemplate but never the Content, so the ContentTemplate is never applied because it's applied only when there's Content set. Instead of DataContext = ci write Content = ci.
By the way the DataContext = ci was useless because the DataContext is already implicitely the object on which the DataTemplate is applied.
Edit
As you're using WPF, use and abuse of its core feature: bindings.
How I would have written your code (if I didn't use full MVVM compliant code):
Your XAML:
<TabControl Grid.Row="0" Name="TabDynamic"
ItemsSource="{Binding TabItems, Mode=OneWay}"
SelectionChanged="tabDynamic_SelectionChanged">
<TabControl.Resources>
<DataTemplate x:Key="TabHeader" DataType="TabItem">
<DockPanel>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Header}" />
<Button Name="btnDelete" DockPanel.Dock="Right" Margin="5,0,0,0" Padding="0" Click="btnTabDelete_Click" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Name}">
<Image Source="{DynamicResource DeleteImg}" Height="11" Width="11"></Image>
</Button>
</DockPanel>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate DataType="viewModels:ConnectionInfoVM">
<TabItem Header="{Binding ConnectionID, Mode=OneWay}"
Name="{Binding ConnectionID, Mode=OneWay}"
HeaderTemplate="{StaticResources TabHeader}">
<StackPanel>
<ScrollViewer Name="Scroller" Background="Black">
<StackPanel>
<TextBlock Text="This line gets printed" Foreground="White" FontFamily="Consolas"/>
<ItemsControl Name="ItemCtrl" ItemsSource="{Binding DataBuffer}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}" Foreground="White" FontFamily="Consolas"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</StackPanel>
</TabItem>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
You cs code become much simpler:
namespace XXXViewer.Views
{
public partial class ConnectionsControl : UserControl
{
private readonly ObservableCollection<ConnectionInfoVM> _tabItems = new ObservableCollection<ConnectionInfoVM>();
public ObservableCollection<ConnectionInfoVM> TabItems {get {return _tabItems;}}
public ConnectionsControl()
{
InitializeComponent();
// bindings
//TabDynamic.ItemsSource = _tabItems;
TabDynamic.DataContext = this;
}
// assume this gets called
private void AddTabItem(ConnectionInfoVM ci)
{
TabItems.Add(ci);
}
}
}
I noted while re-reading your code that you were probably confused about binding in code-behind.
Your code TabDynamic.ItemsSource = _tabItems; is not a binding, it will only set it once.
Anyway, I suggest you read a bit about MVVM. The TabItems should be in a ViewModel class instead of being in code-behind.
The Tabcontrol as per your coding does not contain in it's DataContext the viewmodel but the control; so we need to find a control or something else which holds the VM. It does not appear that the page holds the VM in its DataContext either.
I recommend that one route is to use the TabControl's Tag property to hold the VM such as specifying it in code behind as such:
TabDynamic.ItemsSource = _tabItems;
TabDynamic.DataContext = this;
TabDynamic.Tag = {Wherever you are keeping your VM at this time its not clear in your code example};
Then you can specify Tag from the template binding by specifying the TabControls' name as such:
<ItemsControl Name="ItemCtrl"
ItemsSource="{Binding Tag.DataBuffer,
ElementName=TabDynamic}">
maybe this is a trivial question for many of you...
My app has a TabControl defined as:
<TabControl ItemsSource="{Binding Tabs}" SelectedItem="{Binding SelectedTab}">
<!--Bind the SelectionChanged event of the tab-->
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<!--This is How tab will look-->
<TabControl.ItemTemplate>
<DataTemplate>
<DockPanel>
<Button Name="BtnCloseTab"
DockPanel.Dock="Right"
Margin="5,0,0,0"
Padding="0"
Command="{Binding RelativeSource=
{RelativeSource FindAncestor, AncestorType={x:Type TabControl}},
Path=DataContext.CloseTabCommand}">
<Image Source="/EurocomCPS;component/Images/closeTab.png" Height="11" Width="11"></Image>
</Button>
<TextBlock Text="{Binding Header}" />
</DockPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<!--This will be the content for the tab control-->
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl
ContentTemplateSelector="{StaticResource TemplateSelector}"
Content="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
In the window ViewModel I have the following prop:
private ObservableCollection<Tab> _Tabs;
public CPSViewModel()
{
_Tabs = new ObservableCollection<Tab>();
}
public ObservableCollection<Tab> Tabs
{
get { return _Tabs;}
private set
{
_Tabs = value;
this.RaisePropertyChanged("Tabs");
}
}
Now, when a new Tab is created, the following DataTemplateSelector is called:
class TemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item != null)
{
string templateFile = string.Format("Templates/{0}",
Properties.Settings.Default.AppId + ".tmpl");
if (File.Exists(templateFile))
{
FileStream fs = new FileStream(templateFile, FileMode.Open);
DataTemplate template = XamlReader.Load(fs) as DataTemplate;
return template;
}
}
return null;
}
}
The DataTemplate is based on the XmlDataProvider and here I need to "inform" the Template which xml file it has to load because it is different for every tab:
<DataTemplate
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataTemplate.Resources>
<local:StringToBoolConverter x:Key="StringToBoolConverter" />
<local:StringToIntConverter x:Key="StringToIntConverter" />
<XmlDataProvider x:Key="dataProvider" XPath="func/parametri/param/BLOCKS"/>
</DataTemplate.Resources>
<Grid>
.... controls ....
</Grid>
</DataTemplate>
Is there a way to do it?
EDIT
Substantially what I have to do is to have access to my Tab class into the TemplateSelector.
Regards,
Daniele.
if you could define your tabs like
public class TabFirst:ITab {}
public class TabSecond:ITab {}
public class TabBlup:ITab {}
viewmodel
public ObservableCollection<ITab> Tabs
{
get { return _Tabs;}
private set
{
_Tabs = value;
this.RaisePropertyChanged("Tabs");
}
}
you could get rid of the DataTemplateSelector and just definfe your datatemplates in your resources
<DataTemplate DataType="{x:Type local:TabFirst}">
<view:TabFirstView />
<DataTemplate/>
<DataTemplate DataType="{x:Type local:TabSecond}">
<view:TabSecondView />
<DataTemplate/>
and your content control would be just
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
Say I have the following class, Employee
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public ObservableCollection<Employee> Underlings { get; set; }
}
And then I have the following XAML, bound to an ObservableCollection<Employee> MyEmployees
<ListBox ItemsSource="{Binding Path=MyEmployees}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Tag="{Binding Path=Employee.Id}">
<TextBlock Text="{Binding Path=Employee.Name}"></TextBlock>
<!-- Here's where I declare my underlings -->
<ListBox ItemsSource="{Binding Path=Employee.Underlings}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Tag="{Binding Path=Employee.Id}">
<TextBlock Text="{Binding Path=Employee.Name}"></TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This allows each employee in the collection MyEmployees to have some underlings. But those underlings are also of type employee, and could have their own underlings. How do I cater for those additional levels without making my XAML very complex?
Is there some way to declare my DataTemplate separately and allow it to be referenced within itself?
Do I have to do all this from code-behind instead?
(I realise the XAML above may not be 100% correct, its just an example)
therefore you have to use a TreeView and not a ListBox. And You have to specify a HierarchicalDataTemplate.
You could define the DataTemplate inside the ListBoxes Resources as the default template for Employees:
<ListBox ItemsSource="{Binding MyEmployees}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type myns:Employee}">
<Grid Tag="{Binding Id}">
<TextBlock Text="{Binding Name}"></TextBlock>
<ListBox ItemsSource="{Binding Underlings}" />
</Grid>
</DataTemplate>
</ListBox.Resources>
</ListBox>
I have some XAML code in Window.Resources:
<ContextMenu x:Key="ParentContextMenu">
<MenuItem Header="MenuItem..." Command="{Binding SomeCommand}"/>
</ContextMenu>
<DataTemplate x:Key="ChildDataTemplate" DataType="{x:Type System:String}">
<TextBlock Text="{Binding}" VerticalAlignment="Bottom" />
</DataTemplate>
<HierarchicalDataTemplate x:Key="ParentDataTemplate" DataType="{x:Type ViewModels:IParentViewModel}" ItemsSource="{Binding Path=Agents}" ItemTemplate="{StaticResource ChildDataTemplate}">
<StackPanel Orientation="Horizontal" ContextMenu="{StaticResource ParentContextMenu}">
<TextBlock Text="{Binding ServerName}" />
</StackPanel>
</HierarchicalDataTemplate>
and then
<TreeView ItemsSource="{Binding Items}" ItemTemplate="{StaticResource ParentDataTemplate}"/>
Why is SomeCommand not bound to the Context menu item?
I am sure that DataContext contains a ViewModel because other commands are working well. Any ideas please?
Any problem with this XAML.
So, I think you need to ckeck your class that implements IParentViewModel.
1) To my mind, your SomeCommand like SomeCommand : ICommand. Verify that access modifier is public(public class SomeCommand : ICommand)
2) Verify that access modifier of command property is public
(e.g.
public SomeCommand SomeCommand
{
get { return _someCommand; }
set
{
_someCommand = value;
OnPropertyChanged("SomeCommand");
}
}
3) Verify that you have created command instance (private SomeCommand _someCommand = new SomeCommand();)
Also,do that in case if you have dependency property instead.
Hope it helps.