How to set DataContext in XAML for a dynamically modified ObservableCollection - c#

Inside my class MainWindow I have:
public ObservableCollection<ViewModel> VMs ..
The MainWindow is constructed in the XAML (it creates an empty VMs in the class constructor too):
<Window.Resources>
<c:MainViewModel x:Key="ViewModelsSource"/>
</Window.Resources>
When I click on a button, I add ViewModel objects to the ObservableCollection VMs and the content of the ObservableCollection is shown in a ListBox:
<StackPanel DataContext="{Binding Source={StaticResource ViewModelsSource}}">
<ListBox IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding VMs}"
Background="Transparent"
HorizontalContentAlignment="Stretch"
> ...
The code for the Command Add is:
void AddListExecute()
{
VMs.Add(new ViewModel());
}
The constructor for ViewModel is:
public class ViewModel : MainViewModel
{
//Private Members
private ObservableCollection<FeeViewModel> _fees;
//Properties
public ObservableCollection<FeeViewModel> FVMs
{
get
{
return _fees;
}
set
{
_fees = value;
}
}
//Constructor
public ViewModel()
{
this._fees = new ObservableCollection<FeeViewModel>();
}
...
This part is working fine. Each ViewModel object contains another ObservableCollection:
public ObservableCollection<FeeViewModel> FVMs ..
I have a tabcontrol in the XAML that uses this ObservableCollection to do stuff:
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding FVMs, diag:PresentationTraceSources.TraceLevel=High}"
Style="{StaticResource EnabledTabs}" Grid.Column="1" Margin="0,0,10,0">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
...
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
...
EnabledTabs is a style that uses a property in FeeViewModel:
<Style TargetType="{x:Type TabControl}" x:Key="EnabledTabs">
<Setter Property="IsEnabled" Value="{Binding GotFees}"/>
</Style>
Now I have a binding error, FVMs is null and nothing is shown in the window. If I revert to a previous version without an ObservableCollection of ViewModel objects and I set the DataContext of the TabControl to that single ViewModel everything works fine.
How to set the DataContext of the TabControl to the dynamically created ViewModel objects?
Is it possible to do something like VMs/FVMs in the binding?
Thanks

Solved by adding DataContext to TabControl:
<TabControl
DataContext="{Binding VMs, Source={StaticResource ViewModelsSource}}"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding FVMs, diag:PresentationTraceSources.TraceLevel=High}"
Style="{StaticResource EnabledTabs}" Grid.Column="1" Margin="0,0,10,0">

Related

How to set the focus in a TextBox of a ListView item?

On a UWP app (Windows 10), I am displaying a list of records in a ListView.
When I click on an item, its StackPanel is displayed (using INotifyPropertyChanged).
In the StackPanel, there is a TextBox with some data populated via binding.
I would like that the TextBox automatically receives the focus whenever the StackPanel becomes visible, but I can't find which property or event to use, and how to trigger a textBox.Focus().
Thanks for your feedback on this !
The DataTemplate:
<DataTemplate x:Key="templateList">
<StackPanel>
...
<StackPanel Visibility="{Binding IsSelected}">
<TextBox x:Name="textBox"
Text="{Binding Title, Mode=TwoWay}"/>
...
</StackPanel>
</StackPanel>
</DataTemplate>
...
The ListView:
<ListView x:Name="listView"
ItemsSource="{Binding mylist}"
ItemTemplate="{StaticResource templateList}"/>
I can suggest use Behaviors for this case. As I noticed, you use Visibility type for IsSelected property. It means that we can use DataTriggerBehavior and create our SetupFocusAction which implement IAction
public class SetupFocusAction : DependencyObject, IAction
{
public Control TargetObject
{
get { return (Control)GetValue(TargetObjectProperty); }
set { SetValue(TargetObjectProperty, value); }
}
public static readonly DependencyProperty TargetObjectProperty =
DependencyProperty.Register("TargetObject", typeof(Control), typeof(SetupFocusAction), new PropertyMetadata(0));
public object Execute(object sender, object parameter)
{
return TargetObject?.Focus(FocusState.Programmatic);
}
}
After that we can use this action in XAML:
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
...
<StackPanel Visibility="{Binding IsSelected}"
Grid.Row="1">
<TextBox x:Name="textBox"
Text="{Binding Title, Mode=TwoWay}">
<i:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsSelected}"
ComparisonCondition="Equal"
Value="Visible">
<local:SetupFocusAction TargetObject="{Binding ElementName=textBox}"/>
</core:DataTriggerBehavior>
</i:Interaction.Behaviors>
</TextBox>
</StackPanel>

Data bind an ItemsControl in a DataTemplate

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

MVVM Attached Property View Model

I've got this problem with Attached Properties in MVVM. I'm building a C# WPF application.
I'm filling a ObserverableCollection with custom Classes of the type ClassObject. And from this collection I'm creating ClassShapeViews on a canvas. These ClassShapeViews need a reference to the ClassObject to show correct information.
To pass the ClassObject reference i'm binding to a AttachedProperty in the XAML like this:
<ItemsControl ItemsSource="{Binding ClassObjectList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="canvas" Background="White" AllowDrop="True"
DragEnter="canvas_DragEnter"
DragOver="canvas_DragOver"
Drop="canvas_Drop"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<!-- This part doesn't work-->
<shape:ClassShapeView>
<Style TargetType="shape:ClassShapeView">
<Setter Property="shape:ClassShapeView.ClassObject" Value="{Binding}"/>
</Style>
</shape:ClassShapeView>
<Label Content="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
In the Code behind of the ClassShapeView I'm handeling the DependencyProperty:
public partial class ClassShapeView : UserControl
{
protected ClassShapeViewModel viewModel { get { return DataContext as ClassShapeViewModel; } }
public ClassShapeView()
{
InitializeComponent();
this.DataContext = new ClassShapeViewModel();
}
public static readonly DependencyProperty ClassObjectProperty =
DependencyProperty.RegisterAttached("ClassObject", typeof(ClassObject), typeof(ClassShapeView), new PropertyMetadata(default(ClassObject)));
public static void SetClassObject(UIElement element, ClassObject value)
{
element.SetValue(ClassObjectProperty, value);
}
public static ClassObject GetClassObject(UIElement element)
{
return (ClassObject)element.GetValue(ClassObjectProperty);
}
public ClassObject MyClassObject
{
get { return viewModel.ClassObject; }
set { viewModel.ClassObject = value; }
}
}
After receiving the ClassObject Reference I want to send this reference to the attached ViewModel with the property MyClassObject. The data in the ClassShapeView is bound to the ViewModel.
I just can't figure out how to to transform the ClassObject Reference form the CodeBehind to the ViewModel.
Thanks to Clemens i have the answer
"when the ItemsControl's ItemsSource property is bound to a collection of ClassObject instances (as what I suppose is your ClassObjectList), then the DataContext of each item container (e.g. a ContentPresenter) is set to the respective element from the collection. This DataContext is inherited into the ItemTemplate, so that the controls inside the DataTemplate (e.g. your UserControl) already get the correct ClassObject element as their DataContext."
So my XAML looks like this now
<ItemsControl ItemsSource="{Binding ClassObjectList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="canvas" Background="White" AllowDrop="True"
DragEnter="canvas_DragEnter"
DragOver="canvas_DragOver"
Drop="canvas_Drop"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<shape:ClassShapeView/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And my View looks like this:
public partial class ClassShapeView : UserControl
{
public ClassShapeView()
{
InitializeComponent();
}
}

Access a class instance inside a other class from xaml in wpf

My class looks like this:
public class testclass
{
public List<otherClass> references { get { return _references; } }
}
My otherClass looks like this
public class otherClass
{
public string name { get; set; }
}
And now i try to access this "otherClass" inside a DataTemplate
<DataTemplate x:Key="templateCore" DataType="{x:Type vm:AdminInterfaceViewModel}" >
<GroupBox DataContext="{Binding references }">
...
</DataTemplate>
this works fine, or i think at least, beaucse intellisense autocomplete it. But now how can i get access to the name property of the "otherClass" ?
All you need is to binding the List to a ItemsControl type,such as ListBox,DataGrid etc,and the ItemsControl will use the 'otherClass' instance in the List as the DataContext for each item in it.So you can find a 'mapping' there:
'List<otherClass>'--'ItemsControl'
'otherClass'--'Item'
.
I suppose that 'AdminInterfaceViewModel' is your DataContext,and 'references' is one property of it, so try this:
<DataTemplate x:Key="templateCore" DataType="{x:Type vm:AdminInterfaceViewModel}" >
<GroupBox>
<ListBox ItemsSource="{Binding references}">
<ListBox.ItemTemplate>
<DataTemplate>
<TexBox Text="{Binding name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
</DataTemplate>
> Update:
1.Suppose that you have a MainViewModel which contains a property named MyViewModel in type of 'AdminInterfaceViewModel '.
class MainViewModel
{
public AdminInterfaceViewModel MyViewModel {get; set;}
}
2.You have set the 'MainViewModel' as the DataContext of your Window,then you can use the property 'MyViewModel' in xaml.
<Window>
<Grid>
<ContentControl Margin="20" Content="{Binding MyViewModel}">
</ContentControl>
</Grid>
</Window>
3.Define the DataTemplate in your ResourceDictionary such as 'generic.xaml'.Remove the x:Key then the DataTemplate will automatically applied to every 'AdminInterfaceViewModel' type instance.
<DataTemplate x:Key="templateCore" DataType="{x:Type vm:AdminInterfaceViewModel}" >
<GroupBox>
<ListBox ItemsSource="{Binding references}">
<ListBox.ItemTemplate>
<DataTemplate>
<TexBox Text="{Binding name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
</DataTemplate>
> Tips:
Check this link,it may solve your potential problems:MVVM pattern

Binding issue with DataTemplate?

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.

Categories

Resources