I have a grid displaying the contents of an observableCollection of Persons, and two textboxes showing the properties of the selected row. A master-detail view if you will.
When assigning the observablecollection to the datacontext you can simply do this:
<Grid>
<Grid Background="Gray">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
</Grid.RowDefinitions>
<igWPF:XamDataGrid Grid.Row="0" DataSource="{Binding}" IsSynchronizedWithCurrentItem="True" />
<StackPanel Grid.Row="1" Orientation="Horizontal">
<TextBox Height="21" Width="100" Margin="5,0,5,0" Text="{Binding Name}"></TextBox>
<TextBox Height="21" Width="100" Text="{Binding Age}"></TextBox>
</StackPanel>
</Grid>
</Grid>
The IsSynchronizedWithCurrentItem-property makes sure the selected item in the grid is the one that is treated in the textboxes.
I was wondering if it is possible to do this exact thing when the observablecollection is not in the datacontext directly, but rather is located in a viewmodel (which is assigned to the datacontext of the window).
public class TestViewModel: DependencyObject
{
public TestViewModel(){
Results = new ObservableCollection<Person>();
Results.Add(new Person { Age = "23", Name = "Able" });
Results.Add(new Person { Age = "25", Name = "Baker" });
}
public ObservableCollection<TagDlgmtEntity> Results
{
get { return (ObservableCollection<Person>)GetValue(ResultsProperty); }
set { SetValue(ResultsProperty, value); }
}
public static readonly DependencyProperty ResultsProperty =
DependencyProperty.Register("Results", typeof(ObservableCollection<Person>), typeof(TestViewModel), new PropertyMetadata(null));
}
I would like not to reassign a datacontext on a lower level in the visual tree, or bind with the selectedItem property of the grid.
Is it possible to use this mechanism this way?
Thanks!
Yes, that's possible. Declare your binding like this:
DataSource="{Binding Results}"
Example ViewModel
Assumption: ViewModelBase implements INotifyPropertyChanged (see my comment on the question)
public class MainWindowViewModel : ViewModelBase
{
private readonly List<Person> _results;
public MainWindowViewModel()
{
_results = new List<Person>
{
new Person {Age = "23", Name = "Able"},
new Person {Age = "25", Name = "Baker"}
};
ResultsView = CollectionViewSource.GetDefaultView(_results);
ResultsView.CurrentChanged += (sender, args) => RaisePropertyChanged(() => Name);
}
public ICollectionView ResultsView { get; private set; }
public string Name
{
get
{
return ((Person) ResultsView.CurrentItem).Name;
}
}
}
Related
How to add a RichTextBox to a Tab item so that can be added to a Tab Control and display corresponding content in the RichTextBox dynamically in MVVM format.
ViewModel
private ObservableCollection<TabItem> TabControl()
{
ObservableCollection<TabItem> Tabs= new ObservableCollection<TabItem>();
return Tabs;
}
Controller
private void AddNewTabItem(string selectedItem)
{
try
{
System.Windows.Controls.RichTextBox richtextbox = new System.Windows.Controls.RichTextBox();
richtextbox.Name = "richtextbox" + selectedItem;
BrushConverter BC = new BrushConverter();
richtextbox.Background = (SolidColorBrush)(BC.ConvertFrom("#FF098BBB"));
richtextbox.Foreground = System.Windows.Media.Brushes.WhiteSmoke;
richtextbox.IsReadOnly = true;
TabItem m_tabItem = new TabItem();
m_tabItem.Header = selectedItem;
m_tabItem.Name = "tab" + selectedItem;
if (TabControl.Items.Count == 0)
{
TabControl.Items.Insert(0, m_tabItem);
TabControl.SelectedIndex = msgTracerTabControl.Items.Count - 1;
}
else
{
TabControl.Items.Insert(msgTracerTabControl.Items.Count - 1, m_tabItem);
TabControl.SelectedIndex = msgTracerTabControl.Items.Count - 2;
}
m_tabItem.Content = new System.Windows.Controls.RichTextBox();
m_tabItem.Content = richtextbox;
Tabs.add(m_tabItem);
}
catch (Exception EX)
{
}
}
View
<TabControl Grid.Column="1" Grid.Row="1" ItemsSource="{Binding TabControl}" }"/>
I have used this code and working fine and this is not in MVVM this is WAF Architecture in that i'm using MVVM concept.
You're not thinking MVVM. In a ViewModel you would not directly access UI elements but would rather set up bindings and data templates which would render your Viewmodels correctly. The correct approach is to have 2 viewmodels, 1 to act as a master and the second to act as the underlying DataContext for each tab.
A simple example would be something like this:
MainViewModel
public class MainViewModel : BindableBase
{
private int _tabSuffix;
public ObservableCollection<TextViewModel> TextTabs { get; set; } = new ObservableCollection<TextViewModel>();
public DelegateCommand AddNewTabCommand { get; set; }
public MainViewModel()
{
AddNewTabCommand = new DelegateCommand(OnAddNewTabCommand);
}
private void OnAddNewTabCommand()
{
TextTabs.Add(new TextViewModel()
{
Header = $"Tab #{_tabSuffix++}"
});
}
}
MainView
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="Add new tab item" Command="{Binding AddNewTabCommand}"></Button>
<TabControl Grid.Row="1"
ItemsSource="{Binding TextTabs}"
IsSynchronizedWithCurrentItem="True">
<!-- Defines the header -->
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type so44497239:TextViewModel}">
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<!-- defines the context of each tab -->
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type so44497239:TextViewModel}">
<RichTextBox Background="#FF098BBB" Foreground="WhiteSmoke" IsReadOnly="False" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
TextViewModel
public class TextViewModel : BindableBase
{
public string Header { get; set; }
public Brush BackgroundBrush { get; set; }
public Brush ForegroundBrush { get; set; }
public string Document { get; set; }
}
In this example, the main viewmodel has no knowledge of the View but merely adds items to it's own ObservableCollection. The TabControl itself, through the binding to TextTabs, adds it's own tab items and renders them using the ItemTemplate and ContentTemplate properties.
Download code here
I'm very new to WPF (especially XAML) *
Hello, my app has a class which gets bunch of comma separated string. So I made a List collection to store the strings. Also, I made DataTemplate for the listbox. Here is code.
MainWindow.xaml
...
<DataTemplate x:Key="DataTemplate">
<Grid>
<StackPanel Grid.Column="1" Margin="5">
<StackPanel Orientation="Horizontal" TextBlock.FontWeight="Bold" >
<TextBlock Text="{Binding AAA}" />
</StackPanel>
<TextBlock Text="{Binding BBB}" />
<TextBlock Text="{Binding CCC}" />
</StackPanel>
</Grid>
</DataTemplate>
...
<ListBox x:Name="listbox1" HorizontalAlignment="Left" Height="309" Margin="10,10,0,0" Width="216" BorderThickness="1" VerticalAlignment="Top" ItemTemplate="{DynamicResource HeadlineDataTemplate}"/>
MainWindow.xaml.cs
...
MyClass myClass;
public MainWindow()
{
InitializeComponent();
myClass = new MyClass();
}
...
private void Button_Click(object sender, RoutedEventArgs e)
{
myClass.getData(Textbox1.Text); // I want to convert this to add items to listbox1.
}
MyClass.cs
...
public void getData(string target)
{
List<string> itemList = new List<string>();
...
while(result != null)
{
// This loop gives bunch of comma separated string (more than 100)
itemList.Add(result);
}
// after the loop, itemList has
// itemList[0] = {"AAA1,BBB1,CCC1"}
// itemList[1] = {"AAA2,BBB2,CCC2"}
// itemList[2] = {"AAA3,BBB3,CCC3"}
// ...
// I also can store the strings to List<string[]> using string.split().
}
So how should I do this?
I couldn't find the answer on the internet.
I suggest to create a model class to represent each ListBox Item. In this example I name it MyListBoxItemModel :
public class MyListBoxItemModel
{
public string AAA { get; set; }
public string BBB { get; set; }
public string CCC { get; set; }
}
Then in the getData function, create List of MyListBoxItemModel to be listbox1's ItemsSource :
public void getData(string target)
{
List<MyListBoxItemModel> itemList = new List<MyListBoxItemModel>();
...
while(result != null)
{
var splittedResult = result.Split(',');
itemList.Add(new MyListBoxItemModel{
AAA = splittedResult[0],
BBB = splittedResult[1],
CCC = splittedResult[2]
});
}
listbox1.ItemsSource = itemList;
}
Note that this example only demonstrates minimum amount of code needed to get your data displayed in the ListBox. Not involving various techniques and best practices around WPF development, such as implementing INotifyPropertyChanged interface, MVVM pattern, etc.
you can just bind to public properties, so if you write something like
<TextBlock Text="{Binding BBB}" />
you need an object with a public property "BBB".
so in addition to the answer from har07 you should also use a public property for your list(OberservableCollection)
public OberservableCollection<MyListBoxItemModel> ItemList {get;set;}
then your binding for your itemscontrol can look like this
<ListBox ItemsSource="{Binding ItemList}" ItemTemplate="{DynamicResource HeadlineDataTemplate}"/>
all you need now is the right datacontext ;)
I have a "Hello World" type application.
A listbox.ItemsSource = Players. Players is an ObservableCollection of Player that implements INotifyPropertyChanged.
When I add items in the Players constructor the items are displayed fine.
When I update an existing item the changes are reflected.
But when I add or remove items for the ObserveableCollection the listbox does not reflect the Items added / removed.
xaml in MainPage:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox Name="lstPlayers" ItemsSource="{Binding}" Margin="12,66,13,24" Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<!--<StackPanel Background="#FFD1D1D1">-->
<Grid Margin="0,0,0,0" Grid.RowSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Number}" FontSize="48" />
<TextBlock Grid.Column="1" Text="{Binding Name}" FontSize="48" />
</Grid>
<!--</StackPanel>-->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Button" Height="95" HorizontalAlignment="Left" Margin="183,350,0,0" Name="button1" VerticalAlignment="Top" Width="260" Click="button1_Click" />
<Button Content="Button" Height="72" HorizontalAlignment="Left" Margin="198,472,0,0" Name="button3" VerticalAlignment="Top" Width="232" Click="button3_Click" />
</Grid>
The listbox is bound in the code behind:
lstPlayers.ItemsSource = plys;
Player Class:
using System.ComponentModel;
namespace DatabindList
{
public class TcrPlayer : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
_name = value;
NotifyPropertyChanged("Name");
}
}
}
}
Players class:
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace DatabindList
{
public class TcrPlayers : List<TcrPlayer>
{
public ObservableCollection<TcrPlayers> Items { get; private set; }
public TcrPlayers()
{
//these are displayed
Add(new TcrPlayer { Name = "Christine"});
Add(new TcrPlayer { Name = "Janet"});
Add(new TcrPlayer { Name = "Ian"});
}
public void AddPlayer()
{
//THIS HAS NO EFFECT ON THE LISTBOX
Add(new TcrPlayer { Name = "Newbie", Number = "99" });
}
}
}
I can UPDATE an existing item in the list by using the code behind in the MainPage with
var itm = lstPlayers.SelectedItem as TcrPlayer;
itm.Name = "Fred";
but I can't reflect any changes made to the number of items in the Players collection.
TcrPlayers derives from List<TcrPlayer> and adds items to itself. However, the ObservableCollection<TcrPlayer> property isn't connected to that list in any way whatsoever. Assuming that plys is a TcrPlayers you're not binding to an ObservableCollection at all - you're binding to a non-observable List.
Inheriting from ObservableCollection<TcrPlayer> instead of List<TcrPlayer> and removing the unused property might get you closer to where you want to be.
As an aside, you don't see this pattern used very often because inheriting from a collection class isn't generally considered very useful unless you're adding new functionality. Usually you'd just have an ObservableCollection<TcrPlayer> property of the parent viewmodel and bind the listbox's ItemsSource to that.
Speaking of which, you're also setting ItemsSource twice - in your XAML to {Binding} i.e. the current DataContext, and in the codebehind.
You don't use your ObservableCollection. Instead your TcrPlayers class is a list itself (derives from List<T>).
Either derive from ObservableCollection and remove your Items Property or use Items.Add(..) instead of Add().
E.g.
public class TcrPlayers : ObservableCollection<TcrPlayer>
{
..
or
public class TcrPlayers
{
public ObservableCollection<TcrPlayers> Items { get; private set; }
public TcrPlayers()
{
//these are displayed
Items.Add(new TcrPlayer { Name = "Christine"});
Items.Add(new TcrPlayer { Name = "Janet"});
Items.Add(new TcrPlayer { Name = "Ian"});
}
I'm new to Prism, but I have successfully built several WPF/Mvvm-Light applications. I'm using ViewModel-first instaciation for each View/ViewModel pair. The views are all loaded and deactivated when the application opens. Views are activated as a result of catching an aggregate event aimed at them. This is the first view I've tried to bind to data in a ViewModel. The view displays as expected, except that my listbox is never populated. Only the outline of the listbox is visible. If I change the background color of the listbox, the color of the empty listbox is changed. The ViewModel property has eight rows but none of them are visible. I am able to display hardcoded items in the list box. I know that the view model is loading into the view as the data context, since another textblock is able to bind to a ViewModel property It must be something broken in my listbox xaml. Here is some xaml to review:
<UserControl
x:Class="DxStudioSelect.View.DxStudioFindView"
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"
mc:Ignorable="d"
>
<UserControl.Resources>
<DataTemplate x:Key="DxStudioListTemplate">
<TextBlock Text="{Binding Path=FriendlyForkName}"/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ListBox
Grid.Column="0"
ItemsSource="{Binding DatabaseInstanceList}"
ItemTemplate="{StaticResource DxStudioListTemplate}"
/>
<TextBlock Text="{Binding Path=PageName}" Grid.Column="1" FontSize="32" Foreground="Green" TextAlignment="Right"/>
</Grid>
</UserControl>
Here is the code-behind:
public partial class DxStudioFindView : UserControl, IDxStudioFindView {
public DxStudioFindView() {
InitializeComponent();
}
public IViewModel ViewModel {
get { return (IDxStudioFindViewModel)DataContext; }
set { DataContext = value; }
}
}
Here is the ViewModel:
private readonly IEventAggregator _eventAggregator;
private readonly IUnityContainer _unityContainer;
private readonly IRegionManager _regionManager;
private readonly string _dxStudioDatabaseName;
private readonly HeaderUpdatePayload _headerUpdatePayload = new HeaderUpdatePayload("DxStudio", "Select DxStudio Instance");
public DxStudioFindViewModel(IUnityContainer unityContainer, IRegionManager regionManager, IEventAggregator eventAggregator, IDxStudioFindView view)
: base(view) {
_unityContainer = unityContainer;
_regionManager = regionManager;
_eventAggregator = eventAggregator;
View.ViewModel = this;
if(IsInDesignMode) {
//Design-time, so show fake data
DesignTimeDataLoad();
} else {
//Run-time, so do the real stuff
DesignTimeDataLoad();
_dxStudioDatabaseName = LiteralString.DxStudioDatabaseNameTest;
_eventAggregator.GetEvent<ViewChangeRequestEvent>().Subscribe(DxStudioInstanceChangeRequest, ThreadOption.UIThread, false, target => target.TargetView == LiteralString.DxStudioFind);
}
}
public string PageName { get; set; }
//public string PageName { get { return "Find DxStudio Instance"; } }
private ObservableCollection<IDxStudioInstanceDto> _dxStudioInstanceList = null;
public ObservableCollection<IDxStudioInstanceDto> DxStudioInstanceList {
get { return _dxStudioInstanceList; }
set {
_dxStudioInstanceList = value;
OnPropertyChanged("DxStudioInstanceList");
}
}
private void DxStudioInstanceChangeRequest(ViewChangeRequestPayload payload) {
var region = _regionManager.Regions[RegionNames.Content];
region.Activate(View);
_eventAggregator.GetEvent<ViewChangedHeaderEvent>().Publish(_headerUpdatePayload);
var footerUpdatePayload = new FooterUpdatePayload(FooterDisplayMode.DxStudioSelect, _dxStudioDatabaseName, payload.TargetBackDatabase, payload.TargetBack, string.Empty, LiteralString.ToolboxStart);
_eventAggregator.GetEvent<ViewChangedFooterEvent>().Publish(footerUpdatePayload);
}
private void DesignTimeDataLoad() {
PageName = "Find DxStudio Instance";
DxStudioInstanceList = new ObservableCollection<IDxStudioInstanceDto>() {
new DxStudioInstanceDto("Instance1"),
new DxStudioInstanceDto("Instance2"),
new DxStudioInstanceDto("Instance3"),
new DxStudioInstanceDto("Instance4"),
new DxStudioInstanceDto("Instance5"),
new DxStudioInstanceDto("Instance6"),
new DxStudioInstanceDto("Instance7"),
new DxStudioInstanceDto("Instance8"),
};
}
And here is the data transfer object:
public class DxStudioInstanceDto : IDxStudioInstanceDto {
public string FriendlyForkName { get; private set; }
public DxStudioInstanceDto(string friendlyForkName) { FriendlyForkName = friendlyForkName; }
}
Since I'm completely out of ideas, any suggestion would be helpful.
Thanks
Your list is binding to ItemsSource="{Binding DatabaseInstanceList}" but your view model has the property DxStudioInstanceList.
I've just stuck in a problem to bind collection in ItemsControl with ItemTeplate that contains bounded ComboBox.
In my scenario I need to "generate" form that includes textbox and combobox for each item in collection and let user to update items. I could use DataGrid for that but I'd like to see all rows in edit mode, so I use ItemsControl with custom ItemTemplate.
It's ok to edit textboxes but when you try to change any ComboBox, all other ComboBoxes in other rows will change too.
Is it a bug or feature?
Thanks, Ondrej
Window.xaml
<Window x:Class="ComboInItemsControlSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="480" Width="640">
<Window.Resources>
<CollectionViewSource x:Key="cvsComboSource"
Source="{Binding Path=AvailableItemTypes}" />
<DataTemplate x:Key="ItemTemplate">
<Border BorderBrush="Black" BorderThickness="0.5" Margin="2">
<Grid Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding Path=ItemValue}" />
<ComboBox Grid.Column="2"
SelectedValue="{Binding Path=ItemType}"
ItemsSource="{Binding Source={StaticResource cvsComboSource}}"
DisplayMemberPath="Name"
SelectedValuePath="Value" />
</Grid>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Path=SampleItems}"
ItemTemplate="{StaticResource ItemTemplate}"
Margin="10" />
</Grid>
Window.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel
{
public ViewModel()
{
SampleItems = new List<SampleItem> {
new SampleItem { ItemValue = "Value 1" },
new SampleItem { ItemValue = "Value 2" },
new SampleItem { ItemValue = "Value 3" }
};
AvailableItemTypes = new List<SampleItemType> {
new SampleItemType { Name = "Type 1", Value = 1 },
new SampleItemType { Name = "Type 2", Value = 2 },
new SampleItemType { Name = "Type 3", Value = 3 },
new SampleItemType { Name = "Type 4", Value = 4 }
};
}
public IList<SampleItem> SampleItems { get; private set; }
public IList<SampleItemType> AvailableItemTypes { get; private set; }
}
public class SampleItem : ObservableObject
{
private string _itemValue;
private int _itemType;
public string ItemValue
{
get { return _itemValue; }
set { _itemValue = value; RaisePropertyChanged("ItemValue"); }
}
public int ItemType
{
get { return _itemType; }
set { _itemType = value; RaisePropertyChanged("ItemType"); }
}
}
public class SampleItemType : ObservableObject
{
private string _name;
private int _value;
public string Name
{
get { return _name; }
set { _name = value; RaisePropertyChanged("Name"); }
}
public int Value
{
get { return _value; }
set { _value = value; RaisePropertyChanged("Value"); }
}
}
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Picture
here you can see the result on picture
I believe it's because you're binding to a CollectionViewSource, which tracks the current item. Try binding directly to your list instead, which won't track the current item
<ComboBox Grid.Column="2"
SelectedValue="{Binding Path=ItemType}"
DisplayMemberPath="Name"
SelectedValuePath="Value"
ItemsSource="{Binding RelativeSource={
RelativeSource AncestorType={x:Type ItemsControl}},
Path=DataContext.AvailableItemTypes}" />
While you have a combobox in each row, it doesnt see these comboboxes as being seperate. i.e. They are all using the same collection, and the same selectedValue, so when a value changes in one box, it changes in all of them.
The best way to fix this is to add the SampleItemType collection as a property on your SampleItem model and to then bind the combo box to that property.