I want to create a ListBox inside a UserContorl, and than, using that userControl to show and "manage" that list in many pages.
For example i got a list of trucks, each object truck has some property like the name, the id...
Now i create my own UserControl
<UserControl
x:Class="Crud.View.ListboxInUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Crud.View"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Name="myUserControl"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<ListBox x:Name="aName" ItemsSource="{Binding ??}">
<StackPanel>
<StackPanel Orientation="Vertical" Margin="0,20,0,0">
<TextBlock Text="Id"/>
<TextBlock Text="{Binding Id}" />
</StackPanel>
<StackPanel Orientation="Vertical" Margin="0,20,0,0">
<TextBlock Text="Name"/>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</StackPanel>
</ListBox>
</Grid>
How can i bind the items in the code behind?
And how can i manage the "click" on the list?
In a Page.xaml i want to write something like
<LUC:ListboxInUserControl x:Name="MyListbox DataContext="{Binding}"/>
and in the code behind
private ObservableCollection<Truck> TestList { get; set; }
...
TestList = await TruckService.GetAll(); //a method to get the list
MyListbox.MyItemsSource = TestList;
Add listbox to your UserControl,
<ListBox x:Name="aName" SelectionChanged="aName_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Vertical" Margin="0,20,0,0">
<TextBlock Text="Id"/>
<TextBlock Text="{Binding Id}" />
</StackPanel>
<StackPanel Orientation="Vertical" Margin="0,20,0,0">
<TextBlock Text="Name"/>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Add event handler for getting selection changed and public property of listbox to bind objects in user control code behind,
public event EventHandler<EventArgs> SelectionChangedEvent;
public ListBoxInUserControl()
{
this.InitializeComponent();
}
private void aName_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectionChangedEvent(sender, new EventArgs());
}
private ListBox myVar;
public ListBox MyProperty
{
get { return aName; }
set { aName = value; }
}
Then you can add this usercontrol in your xaml,
<local:ListBoxInUserControl x:Name="uc_ListBoxInUserControl" SelectionChangedEvent="uc_ListBoxInUserControl_SelectionChangedEvent"> </local:ListBoxInUserControl>
In code behind you can bind data ,
uc_ListBoxInUserControl.MyProperty.ItemsSource = TestList;
and access selection changed event,
private void uc_ListBoxInUserControl_SelectionChangedEvent(object sender, EventArgs e)
{
}
Related
I have a ContentControl bound to an instance of class X and a DataTemplate for X in the resource section of the Main Window.
<Window x:Class="DT1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DT1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:X}">
<TextBlock Text="Hello, World!"/>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<Button Content="Press me" Click="Button_Click" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<ContentControl x:Name="cont" Background="Pink" Content="{Binding MyX}" Tag="Blobby"/>
</StackPanel>
</Grid>
</Window>
In the button click handler in the MainWindow code behind I have a handler which tries to access the ContentTemplate of the ContentControl but this is always null:
using System.Windows;
namespace DT1
{
public class X
{
}
public partial class MainWindow : Window
{
public X MyX { get; set; } = new X();
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var template = cont.ContentTemplate;
System.Diagnostics.Debug.WriteLine($"ContentTemplate: {template?.ToString() ?? "template is null"}");
}
}
}
What is the right way to access the visual items in the DataTemplate for X?
It is null because you are not setting any datatemplate explicitly. When you define a datatemplate without providing an x:key and providing a DataType using x:Type you are making use of what it is known as Implicit Datatemplates.
In order to get the datatemplate from codebehind you must assign it explicitly:
<Window.Resources>
<DataTemplate DataType="{x:Type local:X}" x:Key="XTemplate">
<TextBlock Text="Hello, World!"/>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<Button Content="Press me" Click="Button_Click" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<ContentControl x:Name="cont" Background="Pink" Content="{Binding MyX}" Tag="Blobby" ContentTemplate="{StaticResource XTemplate}"/>
</StackPanel>
</Grid>
Hope this helps!
As a new in WPF and MVVM light, I am struggling to apply the MVVM pattern in a TabControl. I will give you an example of what I am trying to achieve.
TabOne xaml and its view model
<UserControl x:Class="TestTabControl.TabOne"
xmlns:local="clr-namespace:TestTabControl"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="tab one ..." FontWeight="Bold" FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</UserControl>
//TabOne ViewModel
class TabOne : ViewModelBase
{
public string TabName
{
get
{
return "TabOne";
}
}
}
TabTwo xaml and its viewmodel
<UserControl x:Class="TestTabControl.TabTwo"
xmlns:local="clr-namespace:TestTabControl"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="tab two ..." FontWeight="Bold" FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</UserControl>
//TabTwo ViewModel
class TabTwo : ViewModelBase
{
public string TabName
{
get
{
return "TabTwo";
}
}
}
and finally the MainWindow xaml and its viewmodel
<Window x:Class="TestTabControl.MainWindow"
xmlns:local="clr-namespace:TestTabControl"
mc:Ignorable="d"
Title="Test Tab Control" MinWidth="500" Width="1000" Height="800">
<TabControl ItemsSource="{Binding TabViewModels}" >
<TabControl.ItemTemplate >
<!-- header template -->
<DataTemplate>
<TextBlock Text="{Binding TabName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
?????????
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Window>
//MainWindow ViewModel
class MainWindowViewModel : ViewModelBase
{
private ObservableCollection<ViewModelBase> _tabViewModels;
public MainWindowViewModel()
{
_tabViewModels = new ObservableCollection<ViewModelBase>();
TabViewModels.Add(new TabOne());
TabViewModels.Add(new TabTwo());
}
public ObservableCollection<ViewModelBase> TabViewModels
{
get
{
return _tabViewModels;
}
set // is that part right?
{
_tabViewModels = value;
RaisePropertyChanged(() => TabViewModels);
}
}
}
What am I supposed to write in the DataTemplate? Can I pass both usercontrols for TabOne and TabTwo in this DataTemplate in order to get the view for each tab I click? Or do I need to write another DataTemplate?
You may already knew the answer by now. But for the benefits of other people, what you need to do is:
<Grid Margin="10">
<Grid.Resources>
<DataTemplate DataType="{x:Type local:TabOne}">
<local:UserControlOne/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:TabTwo}">
<local:UserControlTwo/>
</DataTemplate>
</Grid.Resources>
<TabControl Margin="10"
ItemsSource="{Binding TabViewModels}">
</TabControl>
</Grid>
Please note that, your UserControl for TabOne ViewModel is also named TabOne.
I changed it to UserControlOne. Same applies to UserControlTwo.
I have a List View which has a binding to an ObservableCollection, I am trying to have it so that when a button is pressed the item is removed from the list and the SQLite database, so far it only removes from the database, unless I restart the app then the item is no longer on in the ListView, could someone tell me what I'm doing wrong?
Code Behind:
namespace Epicure.Views
{
public sealed partial class Ingredients : Page
{
public Ingredients()
{
this.InitializeComponent();
TestViewBinding();
this.DataContext = this;
}
public static ObservableCollection<Ingredient> IngredientsCollection = new ObservableCollection<Ingredient>();
public void TestViewBinding()
{
var db = new SQLiteConnection(new SQLitePlatformWinRT(), App.path);
var Ingredients = new List<Ingredient>();
Ingredients = db.Table<Ingredient>().ToList();
foreach (var Ingredient in Ingredients)
{
IngredientsCollection.Add(Ingredient);
}
}
private void ListUpdated(object sender, object e)
{
if (IngredientsCollection.Count == 0)
{
LonelyPanel.Visibility = Visibility.Visible;
}
if (IngredientsCollection.Count > 0)
{
LonelyPanel.Visibility = Visibility.Collapsed;
}
}
private void RemoveClicked(object sender, RoutedEventArgs e)
{
var db = new SQLiteConnection(new SQLitePlatformWinRT(), App.path);
var Ingredients = db.Table<Ingredient>().ToList();
foreach (var Ingredient in Ingredients)
{
db.Delete(Ingredient);
IngredientsCollection.Remove(Ingredient);
}
}
private void NewIngredientClicked(object sender, RoutedEventArgs e)
{
NavigationService.NavigateToPage(typeof(NewIngredient));
}
}
}
XAML:
<Page
x:Class="Epicure.Views.Ingredients"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Epicure.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<ListView x:Name="IngredientList" ItemsSource="{Binding IngredientsCollection}" SelectionMode="Single" BorderThickness="0,1,0.5,0" BorderBrush="#FF007575" LayoutUpdated="ListUpdated">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Height="140" HorizontalAlignment="Stretch">
<StackPanel>
<TextBlock Text="{Binding IngredientName}"/>
<AppBarButton x:Name="DeleteButton" Style="{StaticResource EpicureRibbonButton}" Width="48" Click="RemoveClicked" Icon="Delete"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel x:Name="LonelyPanel" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock x:Name="Icon" TextWrapping="Wrap" TextAlignment="Center" Text="" FontFamily="Segoe MDL2 Assets" FontSize="48" Margin="0" Foreground="#FF007575"/>
<TextBlock x:Name="Text" TextWrapping="Wrap" TextAlignment="Center" Margin="0,12,0,0" Foreground="#FF007575">
<Run Text="It's lonely here,"/>
<LineBreak/>
<Run Text="want to add something?"/>
</TextBlock>
<AppBarButton x:Name="AddIngredient" HorizontalAlignment="Stretch" Label="Add Ingredient" VerticalAlignment="Stretch" Style="{StaticResource AddButton}" Width="Auto" Margin="0,12,0,0" Foreground="#FF007575" Click="NewIngredientClicked">
<AppBarButton.Icon>
<FontIcon Glyph="" FontSize="20"/>
</AppBarButton.Icon>
</AppBarButton>
</StackPanel>
</Grid>
I think to modify observablecollection you should iterate over a copy of the collection.
Try like this where you should compare a property preferably id .
var copy = new ObservableCollection<Ingredient>(IngredientsCollection);
copy.Remove(copy.Where(i => i.Id == Ingredient.Id).Single());
Based on the latest code sample it looks like you are not actually Binding your ItemSource but rather assign it from code which will likely cause your issue.
First update your XAML to correctly bind ItemsSource to the ObservableCollection
<ListView x:Name="IngredientList" ItemsSource="{Binding IngredientsCollection}" SelectionMode="Single" BorderThickness="0,1,0.5,0" BorderBrush="#FF007575" LayoutUpdated="ListUpdated">
Remove IngredientList.ItemsSource = IngredientsCollection; from your TestListViewBinding method as this will be handled by the binding
Ensure that your View's DataContext is set to the class where you declare the Collection. Since it appears to be your code behind, you can just set it in the constructor
public Ingredients()
{
this.InitializeComponent();
this.DataContext = this;
IngredientsCollection = new ObservableCollection<Ingredient>()
TestViewBinding();
};
Make sure your IngredientsCollection is a property and not a field.
public ObservableCollection<Ingredient> IngredientsCollection { get; set; }
Because your Page does not implement INotifyPropertyChanged, the IngredientsCollection needs to be initialized in the constructor so that it gets bound to.
Obviously this is not the ideal way to set up you MVVM but it's a start to get you going.
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}">
When I tap on listBox item i get a SubItem in "selectionChanged" event. I need to get Title as well. How i can achieve it?
public class Data
{
public string Title { get; set; }
public List<SubItem> SubItems { get; set; }
public Data()
{
SubItems = new List<SubItem>();
}
}
<phone:LongListSelector ItemsSource="{Binding DataCollection}" Grid.Row="0">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" Padding="5" />
<TextBlock Text="{Binding ImageSource}" Padding="5"/>
</StackPanel>
<ListBox ItemsSource="{Binding SubItems}" SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SubItemTitle}" Margin="0,0,12,0" Padding="10" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
In the SelectionChanged event, you can retrieve your ListBox by casting the sender parameter. From there, you can retrieve your Data object by casting the datacontext:
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listBox = (ListBox)sender;
var data = (Data)listBox.DataContext;
System.Diagnostics.Debug.WriteLine(data.Title);
}
Under your selectionChangedevent try this :
string text = (listBox.SelectedItem as ListBoxItem).Content.ToString(); //listBox is the name of the Listbox
a better reference could be this:
Getting selected item string from bound ListBox
Hope it helps!