I can not display the item list.
What am I doing wrong?
XAML(View):
<Grid.DataContext>
<vm:MainViewModel />
</Grid.DataContext>
<Grid.Resources>
<DataTemplate x:Key="Remedy">
<TextBlock Text="{Binding Titulo}" />
</DataTemplate>
<HierarchicalDataTemplate x:Key="Laboratorio"
ItemTemplate="{StaticResource Remedy}"
ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Titulo}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="Root"
ItemTemplate="{StaticResource Laboratorio}"
ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Titulo}" />
</HierarchicalDataTemplate>
</Grid.Resources>
<TreeView x:Name="dirR"
ItemsSource="{Binding Path=Arbol}"
ItemTemplate="{StaticResource Root}">
</TreeView>
C# (ViewModel):
class MainViewModel:INotifyPropertyChanged
{
private TreeItem _arbol = new TreeItem("Remedys");
public TreeItem Arbol
{
set
{
_arbol = value;
OnPropertyChanged("Arbol");
}
get { return _arbol; }
}
public MainViewModel()
{
//Populate Arbol
...
}
}
C# (Model):
public class TreeItem:INotifyPropertyChanged
{
private String _titulo;
private ObservableCollection<TreeItem> _items;
public String Titulo
{
get { return _titulo; }
set
{
_titulo = value;
OnPropertyChanged("Titulo");
}
}
public ObservableCollection<TreeItem> Items
{
get { return _items; }
set
{
_items = value;
OnPropertyChanged("Items");
}
}
public TreeItem(String __titulo)
{
Titulo = __titulo;
}
C# (MainWindow v1):
public MainWindow()
{
InitializeComponent();
}
C# (MainWindow v2) This version works ok but I do not like it because there is code dependency between View and Viewmodel
public MainWindow()
{
InitializeComponent();
MainViewModel vm = new MainViewModel();
dirR.Items.Add(vm.Arbol);
}
You can only bind a value to ItemsSource which implements IEnumerable. Generally spoken you have to bind a collection to ItemsSource.
Change your MainViewModel to
class MainViewModel:INotifyPropertyChanged
{
private ObservableCollection<TreeItem> _arbol =
new ObservableCollection<TreeItem>(
new List<TreeItem>
{
new TreeItem("Remedys")
} );
public ObservableCollection<TreeItem> Arbol
{
set
{
_arbol = value;
OnPropertyChanged("Arbol");
}
get { return _arbol; }
}
public MainViewModel()
{
//Populate Arbol
...
}
}
Set DataContext in MainWindow code:
From XAML:
<Window.DataContext>
<vm:MainWindowViewModel/>
</Window.DataContext>
From code:
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
Related
i am writing xamarin forecast weather app with MVVM.
I fill ObservableCollection<DailyWeather> and ObservableCollection<HourlyWeather>.
In ViewModel
private ObservableCollection<DailyWeather> dailyWeather;
public ObservableCollection<DailyWeather> DailyWeather
{
get => dailyWeather;
set
{
dailyWeather = value;
OnPropertyChange();
}
}
Models
public class DailyWeather
{
public int DayOfYear { get; set; }
public ObservableCollection<HourlyWeather> HourlyWeather { get; set; }
}
public class HourlyWeather
{
public string Temperature { get; set; }
public string Time { get; set; }
}
Xaml code
<ListView ItemsSource="{Binding DailyWeather, Source={StaticResource vm}}"
RowHeight="200">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding DayOfYear}"/>
<ListView ItemsSource="{Binding HourlyWeather}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Temperature}"/>
<Label Text="{Binding Time}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Parent ListView outputs collection and "DayOfYear".
Child ListView Outputs Collection and sees object properties "Temperature" and "Time" but not output them, why?
All collections are filled.
Why if i delete ViewCell from ListViews then app thow "specified cast is not valid" exception?
You need to remove this Source={StaticResource vm} from your binding.
Add this to your XAML:
<ContentPage.BindingContext>
<vm:VIEWMODEL></vm:VIEWMODEL>
</ContentPage.BindingContext>
And change parent listview to this:
<ListView ItemsSource="{Binding DailyWeather}" RowHeight="200">
Remove the Source={StaticResource vm} in ItemSource of your ListView. And set the code below in the code behind of your page.
this.BindingContext = this;
The whole code:
public partial class Page1 : ContentPage
{
private ObservableCollection<DailyWeather> dailyWeather;
public ObservableCollection<DailyWeather> DailyWeather
{
get => dailyWeather;
set
{
dailyWeather = value;
}
}
public Page1()
{
InitializeComponent();
DailyWeather = new ObservableCollection<DailyWeather>()
{
new DailyWeather()
{
DayOfYear=2011,
HourlyWeather=new ObservableCollection<HourlyWeather>()
{
new HourlyWeather()
{
Temperature="1",
Time="2011-01-02"
},
new HourlyWeather()
{
Temperature="2",
Time="2011-01-03"
},
new HourlyWeather()
{
Temperature="3",
Time="2011-01-04"
},
new HourlyWeather()
{
Temperature="4",
Time="2011-01-05"
}
}
}
};
this.BindingContext = this;
}
}
public class DailyWeather
{
public int DayOfYear { get; set; }
public ObservableCollection<HourlyWeather> HourlyWeather { get; set; }
}
public class HourlyWeather
{
public string Temperature { get; set; }
public string Time { get; set; }
}
Screenshot:
If you using mvvm , you should use Locator. Here is working example. Changed some tags because dont have WPF now at pc.
YourVM.cs
class YourVM : INotifyPropertyChanged
{
private ObservableCollection<DailyWeather> dailyWeather;
public ObservableCollection<DailyWeather> DailyWeather
{
get => dailyWeather;
set
{
dailyWeather = value;
OnPropertyChange(nameof(DailyWeather));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChange(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public YourVM()
{
DailyWeather = new ObservableCollection<DailyWeather>()
{
new DailyWeather()
{
DayOfYear=2011,
HourlyWeather=new ObservableCollection<HourlyWeather>()
{
new HourlyWeather()
{
Temperature="1",
Time="2011-01-02"
},
new HourlyWeather()
{
Temperature="2",
Time="2011-01-03"
},
new HourlyWeather()
{
Temperature="3",
Time="2011-01-04"
},
new HourlyWeather()
{
Temperature="4",
Time="2011-01-05"
}
}
}
};
}
}
MVVMLocator.cs
class MVVMLocator
{
public MVVMLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<YourVM>();
}
public static YourVM YourVM => SimpleIoc.Default.GetInstance<YourVM>();
}
App.xaml
<Application
x:Class="App2.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App2">
<Application.Resources>
<ResourceDictionary>
<local:MVVMLocator x:Key="Locator"/>
</ResourceDictionary>
</Application.Resources>
</Application>
MainPage.xaml
<Page
x:Class="App2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<ListView ItemsSource="{Binding YourVM.DailyWeather , Source={StaticResource Locator}}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Height="200">
<TextBlock Text="{Binding DayOfYear}"/>
<ListView ItemsSource="{Binding HourlyWeather}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Temperature}"/>
<TextBlock Text="{Binding Time}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
Nuget: CommonServiceLocator , MvvmLightLibs
I have a problem . I can't change databind when I selecte Item in list view
This My my code xaml ( View ):
<ListView ScrollViewer.HorizontalScrollBarVisibility="Disabled" ItemsSource="{Binding FCsource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<Border Margin="10" Width="440" Height="220" >
<Grid>
<TextBlock Text="{Binding Words, Mode = TwoWay}"></TextBlock>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
This My my code in ViewModel:
public ObservableCollection _FCsource;
public ObservableCollection FCsource { get { return AddFlashCard(); } set { FCsource = value; OnPropertyChanged(); } }
private Item _SelectedItem;
public Item SelectedItem { get=>_SelectedItem; set
{
_SelectedItem = value;
OnPropertyChanged();
if(_SelectedItem!=null)
{
SelectedItem.Words="hello"
}
}
}
public WordsViewModel()
{
}
private ObservableCollection<Item> AddFlashCard()
{
ObservableCollection<Item> listmn = new ObservableCollection<Item>();
listmn.Add(new Item("qwda");
listmn.Add(new Item("qwda");
listmn.Add(new Item("qwda");
return listmn;
}
With some changes to your code (you have compile errors in there) it works as expected. Predictably, it stops working if there is no INotifyPropertyChanged interface properly implemented on the Item class (specifically signaling changes of the Words property). That is probably what is causing your issue.
Below is the working code (the INotifyPropertyChanged is implemented here using PropertyChanged.Fody version 2.6.0 nuget package):
[AddINotifyPropertyChangedInterface]
public class WordsViewModel
{
public ObservableCollection<WordItem> _FCsource;
public ObservableCollection<WordItem> FCsource { get { return AddFlashCard(); } set { FCsource = value; } }
private WordItem _SelectedItem;
public WordItem SelectedItem
{
get => _SelectedItem; set
{
_SelectedItem = value;
if (_SelectedItem != null)
{
SelectedItem.Words = "hello";
}
}
}
public WordsViewModel()
{
}
private ObservableCollection<WordItem> AddFlashCard()
{
ObservableCollection<WordItem> listmn = new ObservableCollection<WordItem>();
listmn.Add(new WordItem("qwda"));
listmn.Add(new WordItem("qwda"));
listmn.Add(new WordItem("qwda"));
return listmn;
}
}
[AddINotifyPropertyChangedInterface]
public class WordItem
{
public string Words { get; set; }
public WordItem(string words)
{
Words = words;
}
}
If you implemented INotifyPropertyChanged properly and it's still not working, then are you sure that you assigned your View Model to your View's DataContext?
I have a UI which displays a ListView in a ListView:
<ListView
SelectedIndex="{x:Bind ParentViewModel.SelectedParentIndex, Mode=TwoWay}"
ItemsSource="{x:Bind ParentViewModel.ParentViewModels, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewModels:ParentViewModel">
<StackPanel>
<TextBlock Text="{Binding ParentName}" />
<ListView
SelectedIndex="{x:Bind SelectedChildIndex, Mode=TwoWay}"
ItemsSource="{Binding ChildViewModels, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewModels:ChildViewModel">
<TextBlock Text="{Binding ChildName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
When I click on a parent element the SelectedParentIndex gets set and when I click on a child element the SelectedChildIndex gets set.
My problem is that when I click on a child element i don't know to wich parent element it belongs because the SelectedParentIndex is not set. How can I solve this?
And the flow how it should be:
Just add an event in. Here is a compiled working example.
<ListView
ItemsSource="{Binding ParentViewModels, Mode=OneWay}"
SelectedIndex="{Binding SelectedParentIndex, Mode=TwoWay}"
SelectedItem="{Binding SelectedParent,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate >
<StackPanel>
<TextBlock Text="{Binding ParentName}" />
<ListView
ItemsSource="{Binding ChildViewModels, Mode=OneWay}"
SelectedIndex="{Binding SelectedChildIndex, Mode=TwoWay}"
SelectedItem="{Binding SelectedChild,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ChildName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here are the cs files. Please pay close attention to the structure.
The MasterViewModel is your DataContext for your View. It handles the SelectedParent, the SelectedParentIndex and your parents collection.
public class MasterViewModel : ViewModelBase
{
private ParentViewModel _SelectedParent;
public ParentViewModel SelectedParent
{
get { return _SelectedParent; }
set
{
_SelectedParent = value;
OnPropertyChanged("SelectedParent");
}
}
private int _SelectedParentIndex;
public int SelectedParentIndex
{
get { return _SelectedParentIndex; }
set
{
_SelectedParentIndex = value;
OnPropertyChanged("SelectedParentIndex");
}
}
public ObservableCollection<ParentViewModel> ParentViewModels
{
get; private set;
}
public MasterViewModel()
{
ParentViewModels = new ObservableCollection<ParentViewModel>();
LoadData();
}
private void LoadData()
{
for(int x = 0; x < 10; x++)
{
ParentViewModel parent = new ParentViewModel();
parent.ChildChangedEvent += Parent_ChildChangedEvent;
for(int y = 0; y < 20; y++)
{
ChildViewModel child = new ChildViewModel()
{ ChildName = "Child " + y };
parent.ChildViewModels.Add(child);
}
ParentViewModels.Add(parent);
}
}
private void Parent_ChildChangedEvent(object sender, EventArgs e)
{
SelectedParent = (ParentViewModel)sender;
}
}
Your ParentViewModel contains your SelectedChildIndex, your SelectedChild and your ChildViewModels collection. It also has a name property
Notice that I added an EventHandler to your ParentViewModel. When the SelectedChild is updated, it fires the event off. Then, we handle this event in the MasterViewModel where we can force the SelectedParent to update.
public class ParentViewModel : ViewModelBase
{
public String ParentName { get; set; }
private int _SelectedChildIndex;
public int SelectedChildIndex
{
get { return _SelectedChildIndex; }
set
{
_SelectedChildIndex = value;
OnPropertyChanged("SelectedChildIndex");
}
}
private ChildViewModel _SelectedChild;
public ChildViewModel SelectedChild
{
get { return _SelectedChild; }
set
{
_SelectedChild = value;
OnPropertyChanged("SelectedChild");
if (ChildChangedEvent != null)
{
ChildChangedEvent(this, new EventArgs());
}
}
}
public ObservableCollection<ChildViewModel> ChildViewModels
{
get; private set;
}
public event EventHandler ChildChangedEvent;
public ParentViewModel()
{
ChildViewModels = new ObservableCollection<ChildViewModel>();
}
}
Your ChildViewModel just has a name property.
public class ChildViewModel : ViewModelBase
{
private string _childName;
public string ChildName
{
get { return _childName; }
set
{
_childName = value;
OnPropertyChanged("ChildName");
}
}
}
The ViewModelBase just updates the UI
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
}
I am using MVVM and trying to represent my ViewModel data in View.
I have a class called Track containing list of Variations. I want to represent each variation as a TextBlock using data binding.
I am able to represent a single track as:
<Window.Resources>
<src:Track x:Key="trck"/>
...
</Window.Resources>
<StackPanel DataContext="{Binding Source={StaticResource trck}}" Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding Vars}" Height="53" Width="349">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Background="{Binding Path=color}" Height="15" Width="{Binding Path=elapsedtime}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
I also have a class called TrackList containing collection of Tracks.
I tried to use HierarchicalDataTemplate to represent Hierarchical Data of TrackList.
But it's not working..
I am new to WPF, and I have tried the below things so far:
<DockPanel.Resources>
<DataTemplate DataType="{x:Type src:Variation}">
<TextBlock Background="{Binding Path=color}" Height="15" Width="{Binding Path=elapsedtime}"/>
</DataTemplate>
<HierarchicalDataTemplate DataType = "{x:Type src:Track}" ItemsSource = "{Binding Path=Vars}">
<StackPanel/>
</HierarchicalDataTemplate>
</DockPanel.Resources>
public class TrackList : ViewModel
{
private ICollection<Track> tracks;
private Track selectedTrack;
public string Name
{ get; set; }
public TrackList()
{
this.tracks = new List<Track>();
this.tracks.Add(new Track("T1"));
this.tracks.Add(new Track("T2"));
Name = "Track List";
selectedTrack = tracks.ElementAt(1);
}
public ICollection<Track> Tracks
{
get { return this.Tracks; }
set { this.Tracks = value; }
}
public Track SelectedTrack
{
get { return this.selectedTrack; }
set
{
if (this.selectedTrack != value)
{
this.selectedTrack = value;
this.OnPropertyChanged("SelectedTrack");
}
}
}
}
public class Track : ViewModel
{
private ICollection<Variation> vars;
private Variation selectedVar;
public string Name { get; set; }
public Track()
{
Init();
}
public Track(string p)
{
// TODO: Complete member initialization
this.Name = p;
Init();
}
private void Init()
{
this.vars = new List<Variation>();
this.vars.Add(new Variation("var1", 20, Brushes.Red));
this.vars.Add(new Variation("var2", 60, Brushes.Green));
this.vars.Add(new Variation("var3", 40, Brushes.Khaki));
this.vars.Add(new Variation("var4", 120, Brushes.Aqua));
selectedVar = vars.ElementAt(1);
}
public ICollection<Variation> Vars
{
get { return this.vars; }
set { this.vars = value; }
}
public Variation SelectedVar
{
get { return this.selectedVar; }
set
{
if (this.selectedVar != value)
{
this.selectedVar = value;
this.OnPropertyChanged("SelectedVar");
}
}
}
}
public class Variation : ViewModel
{
public int elapsedtime { get; set; }
public string Name { get; set; }
public System.Windows.Media.Brush color { get; set; }
public Variation(string varname)
{
Name = varname;
}
public Variation(string name, int time, System.Windows.Media.Brush br)
{
// TODO: Complete member initialization
this.Name = name;
this.elapsedtime = time;
this.color = br;
}
}
public abstract class ViewModel : INotifyPropertyChanged
{
private readonly Dispatcher _dispatcher;
protected ViewModel()
{
}
public event PropertyChangedEventHandler PropertyChanged;
protected Dispatcher Dispatcher
{
get { return _dispatcher; }
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged(this, e);
}
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
Please let me know for any farther information.
Thanks
I don't think you need HierarchicalDataTemplate, your tree has known number of levels (TrackList>Track>Variation). You can simply do this:
<DockPanel.Resources>
<DataTemplate DataType="{x:Type src:Variation}">
<TextBlock Background="{Binding Path=color}" Height="15" Width="{Binding Path=elapsedtime}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type src:Track}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<ItemsControl ItemsSource="{Binding Vars}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
</DataTemplate>
</DockPanel.Resources>
<ItemsControl ItemsSource="{Binding Tracks}" />
Where ItemsControl bind to Tracks property of the TrackList (ItemsControl.DataContext = TrackList).
You can represent your hierarchical data using a TreeView:
<TreeView ItemsSource="{Binding Tracks}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type src:Track}" ItemsSource="{Binding Vars}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
I created a new TextBlock class which has ItemsSource property and translates that ItemsSource into "Run" object:
public class MultiTypeDynamicTextBlock : TextBlock
{
public interface ISection
{
Inline GetDisplayElement();
}
public class TextOption : ISection
{
private Run mText;
public TextOption(string aText)
{
mText = new Run();
mText.Text = aText.Replace("\\n", "\n");
}
public Inline GetDisplayElement()
{
return mText;
}
}
public class LineBreakOption : ISection
{
public Inline GetDisplayElement()
{
return new LineBreak();
}
public ISection Clone()
{
return new LineBreakOption();
}
}
public class ImageOption : ISection
{
private InlineUIContainer mContainer;
public ImageOption(string aDisplay)
{
Image lImage;
lImage = new Image();
lImage.Source = new BitmapImage(new Uri(Environment.CurrentDirectory + aDisplay));
lImage.Height = 15;
lImage.Width = 15;
mContainer = new InlineUIContainer(lImage);
}
public Inline GetDisplayElement()
{
return mContainer;
}
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(ObservableCollection<ISection>), typeof(MultiTypeDynamicTextBlock),
new UIPropertyMetadata(new ObservableCollection<ISection>(),
new PropertyChangedCallback(SetContent)));
public ObservableCollection<ISection> ItemsSource
{
get
{
return GetValue(ItemsSourceProperty) as ObservableCollection<ISection>;
}
set
{
if (ItemsSource != null)
ItemsSource.CollectionChanged -= CollectionChanged;
SetValue(ItemsSourceProperty, value);
SetContent();
ItemsSource.CollectionChanged += CollectionChanged;
}
}
private void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
SetContent();
}
private static void SetContent(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DependencyObject lParent = d;
MultiTypeDynamicTextBlock lPanel = lParent as MultiTypeDynamicTextBlock;
if (lPanel != null)
{
lPanel.ItemsSource = e.NewValue as ObservableCollection<ISection>;
}
}
private void SetContent()
{
if (ItemsSource != null)
{
Inlines.Clear();
foreach (ISection lCurr in ItemsSource)
{
Inlines.Add(lCurr.GetDisplayElement());
}
}
}
If I Bind the ItemsSource directly to the DataContext, it works.
But if I bind it to an object that changes at runtime (such as SelectedItem on a ListBox) it doesn't update the text when a new item is selected.
<StackPanel>
<ListBox x:Name="TheList" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel DataContext="{Binding ElementName=TheList, Path=SelectedItem}">
<TextBlock Text="{Binding Title}" FontSize="20"/>
<local:MultiTypeDynamicTextBlock ItemsSource="{Binding Items}"/>
</StackPanel>
</StackPanel>
Any reason why?
In your example, does the SelectedItem has two properties Title and Items? Or is Items a property in your viewmodel? If the answer is the latter, than you can find a solution below.
I don't entirely understand what you mean, but I'll give it a try.
If you mean that the ItemsSource on your custom control isn't set, than you have to point XAML into the right direction.
Below you can find a solution, if this is what you want to achieve.
What I did is pointing the compiler to the right source with this line of code:
ItemsSource="{Binding DataContext.Items, RelativeSource={RelativeSource AncestorType=Window}}"
Here you say that the compiler can find the Binding property in the DataContext of the Window (or any control where you can find the property).
<StackPanel>
<ListBox x:Name="TheList" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel DataContext="{Binding ElementName=TheList, Path=SelectedItem}">
<TextBlock Text="{Binding Title}" FontSize="20"/>
<local:MultiTypeDynamicTextBlock ItemsSource="{Binding DataContext.Items, RelativeSource={RelativeSource AncestorType=Window}}"/>
</StackPanel>
</StackPanel>
Hopefully this helped.
EDIT
The title property will changes when I select another one from the ListBox.
If Items is set to a new ObservableCollection, do you call the OnPropertyChanged event for Items when the SelectedItem changes?
OnPropertyChanged("Items");
Thank you for your help.
I managed to fix this by updating the MultiTypeDynamicTextBlock as follows:
public class MultiTypeDynamicTextBlock : TextBlock
{
public interface ISection
{
Inline GetDisplayElement();
ISection Clone();
}
public class TextOption : ISection
{
private Run mText;
public TextOption(string aText)
{
mText = new Run();
mText.Text = aText.Replace("\\n", "\n");
}
public Inline GetDisplayElement()
{
return mText;
}
public ISection Clone()
{
return new TextOption(mText.Text);
}
}
public class LineBreakOption : ISection
{
public Inline GetDisplayElement()
{
return new LineBreak();
}
public ISection Clone()
{
return new LineBreakOption();
}
}
public class SectionList
{
private ObservableCollection<ISection> mList;
public Action CollectionChanged;
public ObservableCollection<ISection> Items
{
get
{
ObservableCollection<ISection> lRet = new ObservableCollection<ISection>();
foreach (ISection lCurr in mList)
{
lRet.Add(lCurr.Clone());
}
return lRet;
}
}
public int Count { get { return mList.Count; } }
public SectionList()
{
mList = new ObservableCollection<ISection>();
}
public void Add(ISection aValue)
{
mList.Add(aValue);
}
public SectionList Clone()
{
SectionList lRet = new SectionList();
lRet.mList = Items;
return lRet;
}
}
public MultiTypeDynamicTextBlock()
{
}
public static readonly DependencyProperty ItemsCollectionProperty =
DependencyProperty.Register("ItemsCollection", typeof(SectionList), typeof(MultiTypeDynamicTextBlock),
new UIPropertyMetadata((PropertyChangedCallback)((sender, args) =>
{
MultiTypeDynamicTextBlock textBlock = sender as MultiTypeDynamicTextBlock;
SectionList inlines = args.NewValue as SectionList;
if (textBlock != null)
{
if ((inlines != null) && (inlines.Count > 0))
{
textBlock.ItemsCollection.CollectionChanged += textBlock.ResetInlines;
textBlock.Inlines.Clear();
foreach (ISection lCurr in textBlock.ItemsCollection.Items)
{
textBlock.Inlines.Add(lCurr.GetDisplayElement());
}
}
else
{
inlines = new SectionList();
inlines.Add(new TextOption("No value set"));
textBlock.ItemsCollection = inlines;
}
}
})));
public SectionList ItemsCollection
{
get
{
return (SectionList)GetValue(ItemsCollectionProperty);
}
set
{
SectionList lTemp;
if (value == null)
{
lTemp = new SectionList();
lTemp.Add(new TextOption("No value set for property"));
}
else
{
lTemp = value;
}
SetValue(ItemsCollectionProperty, lTemp);
}
}
private void ResetInlines()
{
Inlines.Clear();
foreach (ISection lCurr in ItemsCollection.Items)
{
Inlines.Add(lCurr.GetDisplayElement());
}
}
}
And I update the fields that were Binded to be of type MultiTypeDynamicTextBlock.SectionList
As long as I am using a copy (Clone) it is working, for some reason when I don't clone it removes the value from the display in the list, if someone knows why I would love to learn but I managed to go around it.
the XAML of the window is:
<StackPanel>
<ListBox x:Name="TheList" ItemsSource="{Binding GeneralItems}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}" FontSize="20"/>
<local:MultiTypeDynamicTextBlock ItemsCollection="{Binding Items}" Margin="20,0,0,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel DataContext="{Binding GeneralItems, Path=SelectedItem}">
<TextBlock Text="{Binding Title}" FontSize="20"/>
<local:MultiTypeDynamicTextBlock DataContext="{Binding Items}" ItemsCollection="{Binding}" Margin="20,0,0,0"/>
</StackPanel>
</StackPanel>