Binding issue in UserControl contained in a ListView - c#

My app structure is as follows:
MainPage.xaml has a ListView that has it's ItemSource set to a CollectionViewSource which is populated in code-behind:
MainPage.xaml
<Page.Resources>
...
<CollectionViewSource x:Key="src" IsSourceGrouped="True" />
...
</Page.Resources>
<Grid>
...
<ListView
ItemsSource="{Binding Source={StaticResource src}}"
SelectionMode="None"
ItemTemplate="{StaticResource processTemplate}"
ItemContainerStyle="{StaticResource ListViewItemStyle}">
<ListView.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource groupTemplate}"/>
</ListView.GroupStyle>
</ListView>
...
</Grid>
MainPage.xaml.cs
var cvs = (CollectionViewSource)Resources["src"];
cvs.Source = groups.ToList();
Where groups is a Linq query which is grouping objects by an object property
This is all working fine to display my groups objects in a ListView successfully. Where I have an issue is inside the layout of the individual list items. The template looks like this and includes a Usercontrol defined in another file.
MainPage.xaml
<DataTemplate x:Name="processTemplate">
<Grid>
...
<TextBlock Text="{Binding Path=Process}" ... />
<TextBlock Text="{Binding Path=Description}" ... />
<TextBlock Text="{Binding Path=LastSuccess}" ... />
<Button Grid.Column="1" Grid.RowSpan="3"
Background="{Binding Path=Status,
Converter={StaticResource stbConverter}}" ... />
<local:MinutesOverlay ... Visibility="{Binding Path=Status,
Converter={StaticResource stoConverter}}"
Overdue="{Binding Path=MinutesWarning}"
Alert="{Binding Path=MinutesAlert}"/>
</Grid>
</DataTemplate>
MinutesOverlay.xaml
<Grid>
...
<TextBlock Text="{Binding Path=Overdue}" />
<TextBlock Text="{Binding Path=Alert}" />
...
</Grid>
MinutesOverlay.xaml.cs
public sealed partial class MinutesOverlay : UserControl
{
public MinutesOverlay()
{
this.InitializeComponent();
}
public static readonly DependencyProperty OverdueProperty = DependencyProperty.Register(
"Overdue", typeof(int), typeof(MinutesOverlay), new PropertyMetadata(0));
public static readonly DependencyProperty AlertProperty = DependencyProperty.Register(
"Alert", typeof(int), typeof(MinutesOverlay), new PropertyMetadata(0));
public int Overdue
{
get { return (int)GetValue(OverdueProperty); }
set { SetValue(OverdueProperty, value); }
}
public int Alert
{
get { return (int)GetValue(AlertProperty); }
set { SetValue(AlertProperty, value); }
}
}
My bindings do not work and I cannot figure out how to get them to work. At present, the visibility of the MinutesOverlay control is governed by a binding which works as long as I do not set the Datacontext of the MinutesOverlay. If I do set it by doing this.Datacontext = this then the binding has no effect and the overlay is always visible(it should be collapsed most of the time).
If I set the values of Overdue and Alert without bindings in the MainPage.xaml it works fine.

Have you tried setting a name (x:Name="userControl") on your usercontrol and changed the binding to Text="{Binding Path=Alert, ElementName=userControl}"?

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

Silverlight user control databinding

I am trying to create a user control that contains a list box and I can't figure out how to properly setup the databinding.
In the MainForm.xaml (MyItems is a ObservableCollection defined in the ViewModel):
<my:ItemsList Items="{Binding MyItems}"/>
The user contol:
public partial class ItemsList : UserControl
{
public ItemsList()
{
InitializeComponent();
}
public IEnumerable Items
{
get { return (IEnumerable)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(IEnumerable), typeof(ItemsList), null);
}
And the xaml (namespaces declarations omitted):
<UserControl x:Class="MyApp.Controls.ItemsList">
<phone:LongListSelector ItemsSource="{Binding Items}">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}" />
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</UserControl>
The error I'm getting: BindingExpression path error: 'Items' property not found on 'MyApp.ViewModels.MainViewModel' ?!?
What I was missing was setting the data context for the listbox in the constructor of the user control...
LayoutRoot.DataContext = this;
Check: do you use correct DataContext of your page(must be your ViewModel)?
The user contol must be:
public partial class ItemsList : UserControl
{
public ItemsList()
{
InitializeComponent();
}
public IEnumerable Items
{
get { return (IEnumerable)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(IEnumerable), typeof(ItemsList), new PropertyMetadata(ItemsChanged));
private static void ItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var controll = (ItemsList)d;
var val = (IEnumerable)e.NewValue;
controll.lls.ItemSource = val;
}
Xaml
<UserControl x:Class="MyApp.Controls.ItemsList">
<phone:LongListSelector x:name="lls">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}" />
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</UserControl>
hope its help
In your xaml you need to add a reference to the viewmodel and make it the datacontext for the control.
<UserControl xmlns:local="clr-namespace:"myproject.mynamespace;assembly=myproject">
<UserControl.Resources>
<local:myviewmodel x:key="viewModel"/>
</UserControl.Resources>
<UserControl.DataContext>
<Binding Source="{StaticResource viewModel}"/>
</UserControl.DataContext>
<phone:LongListSelector ItemsSource="{Binding Items}">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}" />
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</UserControl>
Just a note: You can use the DisplayMemberPath="ItemName" attribute instead of a data template unless you need to interact with the textblock in some way.
Hope this helps.

Binding controls inside ListBox to a source other than the source of ListBox

I have a ListBox bound to a source which provides data to the text property of the controls inside. Now I'd like to bind Foreground property of my textboxes to a different source other than the one the main ListBox is bound to!
My listbox is bound to a ObservableCollection and I want my textblock Foreground property bound to textColor which is located in ViewModel
public SolidColorBrush textColor
{
get { return new SolidColorBrush(Colors.Red); }
}
both are in ViewModel class.
I tried using Foreground="{Binding textColor}" but it seems XAML doesn't see it at all, should I do anything in the page so that it can see it, or is it because the parent (ListBox) is using a different source?
Edit :
More details:
I have a DataContext.cs class which I have defined my tables in it.
I have a ViewModel.cs class which I have these in it
public class CViewModel : INotifyPropertyChanged
{
private CDataContext myDB;
public CViewModel(string DBConnectionString)
{
myDB = new CDataContext(DBConnectionString);
}
private ObservableCollection<Groups> _allGroups;
public ObservableCollection<Groups> AllGroups
{
get { return _allGroups; }
set
{
_allGroups = value;
NotifyPropertyChanged("AllGroups");
}
}
public string textColor
{
get { return "Tomato"; }
}
}
Then I have my XAML file MainPage.xaml:
....
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox Margin="0,8,0,0" toolkit:TiltEffect.IsTiltEnabled="True" x:Name="list" ItemsSource="{Binding AllGroups}" HorizontalAlignment="Center" BorderThickness="4">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="Orange" Width="125" Height="125" Margin="6" Tap="Counterlist_OnTap">
<TextBlock Name="gname" Foreground="White" Text="{Binding Name}" VerticalAlignment="Center" HorizontalAlignment="Center" TextAlignment="Center" TextWrapping="Wrap"/>
<TextBlock Name="ccl" Margin="0,0,0,-5" Foreground="{Binding textColor}" Text="{Binding Count}" FontSize="26" VerticalAlignment="Bottom" HorizontalAlignment="Left" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
....
I also set DataContext of my MainPage to ViewModel in code behind:
this.DataContext = App.ViewModel;
The textColor property is part of the CViewModel, not the Groups object that is the datacontext within the ItemTemplate.
Within the ItemTemplate you can reach out to the parent ViewModel with the following Element binding:
<TextBlock Name="ccl" Margin="0,0,0,-5"
Foreground="{Binding ElementName=ContentPanel, Path=DataContext.textColor}"
Text="{Binding Count}" FontSize="26"
VerticalAlignment="Bottom" HorizontalAlignment="Left" />
All you have to do is declare a static class (e.g. a singleton with per-instance access) and explicitly set the property binding to look in that class instead of the parent-bound model.
Bottom line: Just set the Source explicitly through a StaticResource.

How to create a HierarchicalDataTemplate in code-behind?

I have the need to create a HierarchicalDataTemplate for a TreeView in code-behind.
This is what my XAML looks like:
<DataTemplate x:Key="DetailTemplate">
<StackPanel Orientation="Horizontal">
<Image Height="15" Width="15" Source="{Binding Image}" Margin="0,0,5,0"/>
<TextBlock Text="{Binding Text}" />
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="MasterDetailTemplate"
ItemsSource="{Binding SomeSource}"
ItemTemplate="{StaticResource DetailTemplate}">
<StackPanel Orientation="Horizontal">
<Image Height="15" Width="15" Source="{Binding Image}" Margin="0,0,5,0"/>
<TextBlock Text="{Binding Text}" />
</StackPanel>
</HierarchicalDataTemplate>
This is what i got so far in c#:
Image image = new Image();
image.Name = "image";
image.Height = 15;
image.Width = 15;
Binding imageBinding = new Binding("Image");
BindingOperations.SetBinding(image, Image.SourceProperty, imageBinding);
TextBlock textBlock = new TextBlock();
textBlock.Name = "textBlock";
Binding textBinding = new Binding("Text");
BindingOperations.SetBinding(textBlock, TextBlock.TextProperty, textBinding);
StackPanel stackPanel = new StackPanel();
stackPanel.Orientation = Orientation.Horizontal;
stackPanel.Children.Add(image);
stackPanel.Children.Add(textBlock);
DataTemplate dataTemplate = new DataTemplate();
dataTemplate.DataTemplateKey
I am stuck at the DataTemplateKey.
Is this possible to do in code behind?
Where do i go from here to set the x:Key value?
OK In my comment to your question I specified the code behind way of specifying templates. Now to use / refer them with a key when we add them into ResourceDictionaties, we must add them with a Key.
myWindow.Resources.Add("MasterDetailTemplate", dataTemplate);
Instead of myWindow it can be myParentPanel i.e. any ancestor of your tree view.
But there is one issue..
The Key (i.e. the DataTemplate) doesnt exist at design time. You are creating and adding it at runtime.
So if you are referring this datatemplate then
Either refer the resource after it is are added to the resource dictionary.
e.g.
myWindow.Resources.Add(
"MasterDetailTemplate",
dataTemplate);
myTreeView.ItemTemplate
= myWindow.Resources["MasterDetailTemplate"] as HierarchicalDataTemplate;
Refer the dynamically created data template as DynamicResource in XAML. DynamicResource removes the need of pre-existence of MasterDetailTemplate in any resource dictionary.
<TreeView ItemTemplate="{DynamicResource MasterDetailTemplate}" ... >
....
</TreeView>
Hope this helps.
I do that using resources in XAML:
<DataTemplate x:Key="TreeItemTemplate" DataType="{x:Type a:DriveStatusVar}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding PathName, NotifyOnSourceUpdated = True, NotifyOnTargetUpdated=True, Mode=TwoWay}" FontSize="10" Style="{StaticResource textBlockStyle}" IsEnabled="True"/>
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="TreeModTemplate" DataType="{x:Type a:ModuleGroup}" ItemsSource="{Binding Items}">
<StackPanel Orientation="Horizontal">
<Image Source="{StaticResource add}" Width="15" Height="15"></Image>
<TextBlock Text="{Binding Name, NotifyOnSourceUpdated = True, NotifyOnTargetUpdated=True, Mode=TwoWay}" Style="{StaticResource textBlockStyle}" />
<TextBlock Text=" [" Foreground="Black" />
<TextBlock Text="{Binding Items.Count}" Foreground="Black" />
<TextBlock Text=" Items]" Foreground="Black" />
</StackPanel>
</HierarchicalDataTemplate>
and use them in code behind where define and create theTreeView object:
TreeView tree = new TreeView();
HierarchicalDataTemplate hdt = (HierarchicalDataTemplate)this.Resources["TreeModTemplat"];
hdt.ItemTemplate = (DataTemplate)this.Resources["TreeItemTemplate"];
tree.ItemTemplate = hdt;
//add itemsource
tree.ItemsSource = modList;
modList is a list of ModuleGroup class with a list Items.
Items is a list of DriveStatusVar class
internal class ModuleGroup : INotifyPropertyChanged, ICloneable
{
private bool _isSelected;
private bool _isExpanded;
private bool _isEdited;
private string _name;
private string _codesysName;
private int _codesysId;
private int _bits;
private int _level;
public ObservableCollection<DriveStatusVar> Items { get; set; }
public ObservableCollection<Alarm> Alarms { get; set; }
public List<string> States { get; set; }
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
and so on... if anybody need more code, please tell me! this is just a piece of them.

Categories

Resources