MVVM based Item binding to two Windows Phone controls - c#

I have two Controls on a single Page :
1. RadSlider
2. ListBox
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="100"></RowDefinition>
</Grid.RowDefinitions>
<telerik:RadSlideView Name="imgSlidView" >
<telerik:RadSlideView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Src}"></Image>
</DataTemplate>
</telerik:RadSlideView.ItemTemplate>
<telerik:RadSlideView.ItemPreviewTemplate>
<DataTemplate>
<telerik:RadBusyIndicator></telerik:RadBusyIndicator>
</DataTemplate>
</telerik:RadSlideView.ItemPreviewTemplate>
</telerik:RadSlideView>
<ListBox Grid.Row="1" Name="lstImage">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Image Height="100" Margin="0,0,5,0" Source="{Binding Src}"></Image>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I want to bind the two controls to a single Item Source such that if selection of one changes other's selection also should change . I am interested in MVVM based solution.
My code :
class CategoryViewModel : ViewModelBase
{
public ObservableCollection<ImageSource> ImageCollection { get; set; }
private ImageSource _CurrentImage;
public ImageSource CurrentImage
{
get { return _CurrentImage; }
set
{
_CurrentImage = value;
RaisePropertyChanged("CurrentImage");
}
}
}
In addition to this I have a piece of code that returns IEnumerable and I want this to be as Item Source.
public static async Task<IEnumerable<Object>> GetCategoryNames()
{
if (Categories == null)
{
JDir dir = Newtonsoft.Json.JsonConvert.DeserializeObject<JDir>(await LoadFromJson());
Categories = ConvertJDirToCategory(dir);
return Categories.Select(p => new { Name = p.Name, Src = "Images/" + p.Name + ".jpg" });
}
else
{
return Categories.Select(p => new { Name = p.Name, Src = "Images/" + p.Name + ".jpg" });
}
}
Am I doing in right way ? How should I proceed ?
Thanks in advance !
EDIT - from comments:
private static async Task<string> LoadFromJson()
{
string theData = string.Empty;
StorageFile file = await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///data.json"));
using (StreamReader streamReader = new StreamReader(await file.OpenStreamForReadAsync()))
{
return await streamReader.ReadToEndAsync();
}
}

Thanks every body, My problem has been solved now :
XAML :
DataContext="{Binding Category, Source={StaticResource Locator}}"
Two Controls :
<telerik:RadSlideView Name="imgSlidView" SelectedItem="{Binding SelectedItem,Mode=TwoWay}" ItemsSource="{Binding Images}">
<telerik:RadSlideView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}">
<telerik:RadContextMenu.ContextMenu>
<telerik:RadContextMenu IsZoomEnabled="False" OpenGesture="Tap">
<telerik:RadContextMenuItem Tap="RadContextMenuItem_Tap" Content="Share">
</telerik:RadContextMenuItem>
</telerik:RadContextMenu>
</telerik:RadContextMenu.ContextMenu>
</Image>
</DataTemplate>
</telerik:RadSlideView.ItemTemplate>
<telerik:RadSlideView.ItemPreviewTemplate>
<DataTemplate>
<telerik:RadBusyIndicator></telerik:RadBusyIndicator>
</DataTemplate>
</telerik:RadSlideView.ItemPreviewTemplate>
</telerik:RadSlideView>
<ListBox Grid.Row="1" ScrollViewer.HorizontalScrollBarVisibility="Auto" Name="lstImage" SelectedItem="{Binding SelectedItem,Mode=TwoWay}" ItemsSource="{Binding Images}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Image Height="100" Margin="0,0,5,0" Source="{Binding}">
</Image>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
View Model :
public class CategoryViewModel : ViewModelBase
{
private string _CategoryName;
public string CategoryName
{
get { return _CategoryName; }
set
{
DispatcherHelper.CheckBeginInvokeOnUI(() => { Set<string>(ref _CategoryName, value); });
}
}
private Uri _SelectedItem;
public Uri SelectedItem
{
get { return _SelectedItem; }
set
{
DispatcherHelper.CheckBeginInvokeOnUI(() => { Set<Uri>(ref _SelectedItem, value); });
}
}
private ObservableCollection<Uri> _Images;
public ObservableCollection<Uri> Images
{
get { return _Images; }
set { Set<ObservableCollection<Uri>>(ref _Images, value); }
}
public CategoryViewModel()
{
CategoryName = string.Empty;
Images = new ObservableCollection<Uri>();
}
}
XAML.cs
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
string Category = string.Empty;
NavigationContext.QueryString.TryGetValue("category", out Category);
if (this.DataContext is CategoryViewModel)
{
var vm = (CategoryViewModel)this.DataContext;
vm.Images.Clear();
JSONHelper.LoadFromJson().ContinueWith(t =>
{
vm.CategoryName = Category;
var images = t.Result.Dirs.FirstOrDefault(p => p.DirName == Category).Files;
Dispatcher.BeginInvoke(() =>
{
foreach (var img in images)
{
vm.Images.Add(new Uri(string.Format("Data/{0}/{1}", Category, img), UriKind.Relative));
}
});
});
}
}

Related

clickable button to get data wpf mvvm

View.Xaml
<Grid>
<ListView ItemsSource = "{Binding Path = dcCategory}" SelectedValuePath = "Key" SelectedValue = "{Binding Path = Category, Mode = TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal" >
<Button Content="Add Value" Command="{Binding Path=DataContext.AddValue, RelativeSource= {RelativeSource FindAncestor, AncestorType={x:Type ListView}}}"/>
<TextBlock Text="{Binding Path=Key.Name}"/>
</StackPanel>
<ListBox ItemsSource="{Binding Path=Value}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</Grid>
My goal is to click Add Value and send selected item (Category type). Its right now it's working but not as I acepted.
Insted of clicking only button, I have to click first blue area and then code 'catch' the 'Category' with data. Otherwise Category is null.
example
ViewModel
private Category _Category;
public Category Category
{
get
{
return _Category;
}
set
{
if (_Category != value)
{
_Category = value;
OnPropertyChanged(() => Category);
}
}
}
public ICommand AddValue
{
get
{
if (_AddValue == null)
{
_AddValue = new BaseCommand(() => Messenger.Default.Send(CategoryValueCode.AddValue + "," + Category.CategoryId));
}
return _AddValue;
}
}
That's logic, because your button's command will be executed before ListView.SelectedValue is set. You can change it, if you handle PreviewMouseDown for the Button. I also found it better to set ListView.SelectionMode to Single.
<ListView ItemsSource = "{Binding Path = dcCategory}" SelectedValuePath = "Key" SelectedValue = "{Binding Path = Category, Mode = TwoWay}" SelectionMode="Single">
<Button Content="Add Value" Command="{Binding Path=DataContext.AddValue, RelativeSource= {RelativeSource FindAncestor, AncestorType={x:Type ListView}}}" PreviewMouseDown="PreviewMouseDown"/>
private void PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
ListViewItem lvi = null;
var visParent = VisualTreeHelper.GetParent(sender as FrameworkElement);
while (lvi == null && visParent != null)
{
lvi = visParent as ListViewItem;
visParent = VisualTreeHelper.GetParent(visParent);
}
if (lvi == null) { return; }
lvi.IsSelected = true;
}
I didn't check Rekshino solution
Thank you for the tips, in the meantime during fighting with the problem I made so many changes that completely change viewmodel / view.
I achieved my goal in that way:
View:
<Grid>
<ItemsControl ItemsSource = "{Binding listCategoryAddValue}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="Add Value" Command="{Binding Path=AddValue}"/>
<TextBlock Text="{Binding Category.Name}"/>
</StackPanel>
<ListBox ItemsSource="{Binding ValueList}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal">
</StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
ViewModel:
public class CategoriesViewModel : WszystkieViewModel<CategoryAddValue>
{
#region Fields & Properties
private ObservableCollection<CategoryAddValue> _listCategoryAddValue;
public ObservableCollection<CategoryAddValue> listCategoryAddValue
{
get
{
if (_listCategoryAddValue == null) { Load(); }
return _listCategoryAddValue;
}
set
{
if (_listCategoryAddValue != value)
{
_listCategoryAddValue = value;
OnPropertyChanged(() => listCategoryAddValue);
}
}
}
#endregion Fields & Properties
#region Constructor
public CategoriesViewModel() : base()
{
base.DisplayName = "Kategorie";
}
#endregion Constructor
#region Helpers
private void SendValue(int CategoryId)
{
Messenger.Default.Send(CategoryValueCode.AddValue + "," + CategoryId);
}
public override void Load()
{
var allCategories = (from k in db.Category select k).ToList();
_listCategoryAddValue = new ObservableCollection<CategoryAddValue>();
foreach (var i in allCategories)
{
_listCategoryAddValue.Add(new CategoryAddValue(new RelayCommand(() => SendValue(i.KategoriaId)))
{
Category = i,
ValueList = db.CategoryValue.Where(x => x.CategoryId== i.CategoryId).Select(x => x.Value).ToList()
});
}
}
#endregion Helpers
}
Model
public class CategoryAddValue
{
public Category Category { get; set; }
public List<string> ValueList { get; set; }
private ICommand _addValue;
public ICommand AddValue
{
get
{
return _addValue;
}
}
public CategoryAddValue(RelayCommand command)
{
_addValue = command;
}
}

VariableSizedGridView Windows Phone 8.1

I am writing a Windows Phone 8.1 App.
It has GridView, First row must show 2 items, 2nd and 3rd row must show 3 items each and rest rows must show 4 items. I am trying to use VariableSizedGridView:
Click Here to View Image
but my items are being shown in 1 column like a long list.
Click to view ScreenShot
XAML:
<UserControls:VariableSizedGridView x:Name="ModelRingsGridViewVariable" HorizontalAlignment="Stretch">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid ItemHeight="180"
ItemWidth="20" Orientation="Horizontal"/>
<!--ItemWidth="{Binding ElementName=modelPage, Path=WidthCalculator}"-->
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemTemplate>
<DataTemplate>
<Grid Background="White" Margin="2">
<StackPanel Orientation="Vertical">
<Image Height="100" VerticalAlignment="Top" Source="{Binding ImageLocation}" Stretch="Fill"></Image>
<Grid Background="{StaticResource LightGreenBlueColor}" VerticalAlignment="Bottom"
HorizontalAlignment="Stretch">
<TextBlock Margin="0,8,0,8" VerticalAlignment="Center"
HorizontalAlignment="Center" FontSize="18"
Text="{Binding ImageName}" Foreground="White"></TextBlock>
</Grid>
</StackPanel>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</UserControls:VariableSizedGridView>
C#:
private void GenerateGridViewData()
{
ModelRingsGridViewVariable.Width = Window.Current.Bounds.Width;
ObservableCollection<ModelClass> ModelRingsGridViewData = new ObservableCollection<ModelClass>();
int j = 1;
Uri location = new Uri("ms-appx:///Images/TestImages/TestImageRing.png", UriKind.RelativeOrAbsolute);
for (int i=0; i<20;i++)
{
if(i==0 || i== 1)
{
ModelRingsGridViewData.Add(new ModelClass { ImageName = "RING " + j.ToString(), ImageLocation = location, ColSpan=6 });
}
else if(i== 2 || i==3 || i==4)
{
ModelRingsGridViewData.Add(new ModelClass { ImageName = "RING " + j.ToString(), ImageLocation = location, ColSpan=4 });
}
else
{
ModelRingsGridViewData.Add(new ModelClass { ImageName = "RING " + j.ToString(), ImageLocation = location,ColSpan=3 });
}
j++;
}
ModelRingsGridViewVariable.ItemsSource = ModelRingsGridViewData;
}
Model:
public class ModelClass
{
public Uri ImageLocation { get; set; }
public String ImageName { get; set; }
public int ColSpan { get; set; }
}
VariableSizedGridView.cs:
public class VariableSizedGridView : GridView
{
protected override void PrepareContainerForItemOverride(DependencyObject element,
object item)
{
try
{
dynamic localItem = item;
// element.SetValue(VariableSizedWrapGrid.RowSpanProperty, localItem.RowSpan);
element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, localItem.ColSpan);
}
catch
{
// element.SetValue(VariableSizedWrapGrid.RowSpanProperty, 1);
element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, 1);
}
base.PrepareContainerForItemOverride(element, item);
}
}

WPF AvalonEdit does not display bound TextDocument from ViewModel

I have no Binding errors in my output window.
I want to bind text from a file(s) to one/many avalonEdit controls via MVVM.
How can I do that, because its not working the way I tried?!
VIEW
>
<Window.Resources>
<DataTemplate x:Key="NormalTemplate">
<Expander Margin="0" Header="{Binding FileName}">
<!--<TextBox TextWrapping="Wrap" AcceptsReturn="True" IsReadOnly="True" Text="{Binding Content}" Margin="0"/>-->
<avalonEdit:TextEditor Document="{Binding Document, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></avalonEdit:TextEditor>
</Expander>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40px"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid Background="Orange">
<StackPanel Orientation="Horizontal">
<Button>Run</Button>
<Button Command="{Binding SaveCommand}">Save</Button>
</StackPanel>
</Grid>
<Grid Grid.Row="1">
<ScrollViewer ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ItemsControl ItemsSource="{Binding ErrorLogs}" HorizontalContentAlignment="Stretch" ItemTemplate="{DynamicResource NormalTemplate}" />
</ScrollViewer>
</Grid>
</Grid>
</Window>
VIEWMODEL
public class ErrorLogViewModel : BaseViewModel
{
public string FileName { get; set; }
public string Content { get; set; }
private TextDocument _document = null;
public TextDocument Document
{
get { return this._document; }
set
{
if (this._document != value)
{
this._document = value;
RaisePropertyChanged("Document");
}
}
}
}
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
GetErrorLogs();
SaveCommand = new RelayCommand(Save);
}
public RelayCommand SaveCommand { get; private set; }
private void Save()
{
var text = ErrorLogs[0].Document.Text;
// I get at least the Original loaded text
}
private void GetErrorLogs()
{
for (int i = 0; i < 30; i++)
{
using (FileStream fs = new FileStream("text.txt", FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (StreamReader reader = FileReader.OpenStream(fs, Encoding.UTF8))
{
var doc = new TextDocument(reader.ReadToEnd());
var e = new ErrorLogViewModel { FileName = "ErrorLog " + i, Document = doc };
ErrorLogs.Add(e);
}
}
}
}
private ObservableCollection<ErrorLogViewModel> _errorLogs = new ObservableCollection<ErrorLogViewModel>();
public ObservableCollection<ErrorLogViewModel> ErrorLogs
{
get { return _errorLogs; }
set
{
_errorLogs = value;
this.RaisePropertyChanged("ErrorLogs");
}
}
}
I have looked at that sample were MVVM is introduced:
http://www.codeproject.com/Articles/570313/AvalonDock-Tutorial-Part-AvalonEdit-in-Avalo
I do the same binding actually when I load a text file but my text is just not shown :/
You can find here a repro visual studio solution:
http://www.file-upload.net/download-9067428/ExpanderTest.7z.html

DataTemplate is not generating ListItemBox

I want to generate ListItemBox using DataTemplate but items are not generating. Please guide me where is the mistake. I have following code in MainWindow.xaml.
<Window x:Class="Offline_Website_Downloader.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:bd="clr-namespace:Offline_Website_Downloader"
Title="Offline Website Downloader" Background="#f5f6f7" Height="500" Width="800"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<bd:BindingController x:Key="BindingControllerKey" />
<DataTemplate x:Key="DownloadedWebsitesListBox">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" Width="Auto">
<TextBlock FontWeight="Bold" FontSize="18" Width="480">
<Hyperlink NavigateUri="http://google.com">
<Label Content="{Binding Path=WebsiteTitle}" />
</Hyperlink>
</TextBlock>
<TextBlock Width="132" TextAlignment="right">
<TextBlock Text="Remaining Time: "/>
<TextBlock Name="TimeRemaining" Text="js"/>
</TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal">
<ProgressBar Name="progress1" Maximum="100" Minimum="0" Value="30" Background="#FFF" Width="612" Height="10" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" Width="450">Status: <TextBlock Text="{Binding Path=Status}"/></TextBlock>
<TextBlock Width="162" TextAlignment="right">
<TextBlock Text="Downloading Speed: "/>
<TextBlock Name="DownloadingSpeed" Text="{Binding Path=DownloadingSpeed}"/>
</TextBlock>
</StackPanel>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox Width="Auto"
Name="WebsiteList"
Grid.Column="1"
Grid.Row="2"
Grid.RowSpan="2"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource DownloadedWebsitesListBox}"
Margin="0,0,0,0">
</ListBox>
</Grid>
</window>
and MainWindow.xaml.cs
BindingController bc = new BindingController();
public MainWindow()
{
InitializeComponent();
bc.DownloadingSpeed = "40kb/s";
bc.WebsiteTitle = "WebsiteTitle";
bc.Status = "Downloading";
DataContext = bc;
}
and BindingController.cs
public class BindingController
{
public BindingController()
{
}
private string _WebsiteTitle;
public string WebsiteTitle
{
set { _WebsiteTitle = value; }
get { return _WebsiteTitle; }
}
private string _Status;
public string Status
{
set { _Status = value ; }
get { return _Status ; }
}
private string _DownloadStartDate;
public string DownloadStartDate
{
set { _DownloadStartDate = value; }
get { return _DownloadStartDate ; }
}
private string _DownloadingSpeed = "0 kb/s";
public string DownloadingSpeed
{
set { _DownloadingSpeed = value; }
get { return _DownloadingSpeed; }
}
}
Your problem is that you're binding a ListBox to an object that contains several properties, but really only represents a single object/state. The ListBox expects to display a list of items (i.e. IList, IBindingList, IEnumerable, ObservableCollection).
Assuming you want to display more than one download at a time, which I'm assuming given that you're using a ListBox, I would refactor the download properties into a separate class. You will also need to implement INotifyPropertyChanged on your properties so that when the values are changed, they will be shown in the UI.
public class Download : INotifyPropertyChanged
{
private string _WebsiteTitle;
public string WebsiteTitle
{
get { return _WebsiteTitle; }
set
{
if (_WebsiteTitle == value)
return;
_WebsiteTitle = value;
this.OnPropertyChanged("WebsiteTitle");
}
}
private string _Status;
public string Status
{
get { return _Status; }
set
{
if (_Status == value)
return;
_Status = value;
this.OnPropertyChanged("Status");
}
}
private string _DownloadStartDate;
public string DownloadStartDate
{
get { return _DownloadStartDate; }
set
{
if (_DownloadStartDate == value)
return;
_DownloadStartDate = value;
this.OnPropertyChanged("DownloadStartDate");
}
}
private string _DownloadingSpeed = "0 kb/s";
public string DownloadingSpeed
{
get { return _DownloadingSpeed; }
set
{
if (_DownloadingSpeed == value)
return;
_DownloadingSpeed = value;
this.OnPropertyChanged("DownloadingSpeed");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The new BindingController:
public class BindingController
{
public BindingController()
{
this.Downloads = new ObservableCollection<Download>();
}
public ObservableCollection<Download> Downloads { get; private set; }
}
Setting up the bindings in XAML:
<ListBox Width="Auto"
Name="WebsiteList"
Grid.Column="1"
Grid.Row="2"
Grid.RowSpan="2"
ItemsSource="{Binding Downloads}"
ItemTemplate="{StaticResource DownloadedWebsitesListBox}"
Margin="0,0,0,0">
</ListBox>
Initializing the collection in MainWindow
Download download = new Download();
download.DownloadingSpeed = "40kb/s";
download.WebsiteTitle = "WebsiteTitle";
download.Status = "Downloading";
bc.Downloads.Add(download);
this.DataContext = bc;

Changing DataContext doesn't update list fields

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>

Categories