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;
}
}
Related
I'm trying to build a generic Command that can access properties from my ViewModel. On my Window are several TextBoxes, each TextBoxhas a Button next to it. When the Button is clicked I show an OpenFileDialog and set the Text of the TextBox to the selected files path. The TextBoxitself has a Binding to a property in the ViewModel. Currently this is implemented with a Command in the ViewModel. The Buttons all call the same Commandbut each Button has a its property CommandParameter set to the TextBox that will receive the filepath. The drawback of this is, that I need to cast the parameter from the Commandexecution to a TextBox and then set its Textproperty. My question is now, if I can't somehow bind my 'Command' to the same property the TextBoxis bound to. Here is what I currently do:
XAML
<TextBox Text="{Binding SettingsPath}" x:Name="txtSettingsPath"></TextBox>
<Button Command="{Binding OpenFile}"
CommandParameter="{Binding ElementName=txtSettingsPath}">...</Button>
C#
public ICommand OpenFile
{
get
{
bool CanExecuteOpenFileCommand()
{
return true;
}
CommandHandler GetOpenFileCommand()
{
return new CommandHandler((o) =>
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Multiselect = false;
if (!string.IsNullOrEmpty(SettingsPath) && File.Exists(settingsPath))
{
ofd.InitialDirectory = Path.GetDirectoryName(SettingsPath);
}
if(ofd.ShowDialog() == true)
{
if(o is TextBox txt)
{
txt.Text = ofd.FileName;
}
}
}, CanExecuteOpenFileCommand);
}
return GetOpenFileCommand();
}
}
In the XAML I would like to have something like this:
<TextBox Text="{Binding SettingsPath}"></TextBox>
<Button Command="{Binding OpenFile}"
CommandParameter="{Binding SettingsPath}">...</Button>
Here's what I was talking about in comments:
The "little viewmodel". I added a Label property because in my test project, they all looked the same. That doesn't have to be part of this viewmodel.
public class SettingsPathSelectorViewModel : ViewModelBase
{
#region Label Property
private String _label = default(String);
public String Label
{
get { return _label; }
set
{
if (value != _label)
{
_label = value;
OnPropertyChanged();
}
}
}
#endregion Label Property
#region SettingsPath Property
private String _settingsPath = null;
public String SettingsPath
{
get { return _settingsPath; }
set
{
if (value != _settingsPath)
{
_settingsPath = value;
OnPropertyChanged();
}
}
}
#endregion SettingsPath Property
public ICommand OpenFile
{
get
{
bool CanExecuteOpenFileCommand()
{
return true;
}
// We're no longer using the parameter, since we now have one
// command per SettingsPath.
CommandHandler GetOpenFileCommand()
{
return new CommandHandler((o) =>
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Multiselect = false;
if (!string.IsNullOrEmpty(SettingsPath) && System.IO.File.Exists(SettingsPath))
{
ofd.InitialDirectory = System.IO.Path.GetDirectoryName(SettingsPath);
}
if (ofd.ShowDialog() == true)
{
SettingsPath = ofd.FileName;
}
}, o => CanExecuteOpenFileCommand());
}
return GetOpenFileCommand();
}
}
}
A quickie main viewmodel for demo purposes. We'll illustrate two different ways you could expose these things: Either as named properties, or a collection of varying size displayed in an ItemsControl.
public class MainViewModel : ViewModelBase
{
public SettingsPathSelectorViewModel FirstPath { get; } = new SettingsPathSelectorViewModel() { Label = "First Path" };
public SettingsPathSelectorViewModel SecondPath { get; } = new SettingsPathSelectorViewModel() { Label = "Second Path" };
public ObservableCollection<SettingsPathSelectorViewModel> SettingsPaths { get; } = new ObservableCollection<SettingsPathSelectorViewModel>
{
new SettingsPathSelectorViewModel() { Label = "First Collection Path" },
new SettingsPathSelectorViewModel() { Label = "Second Collection Path" },
new SettingsPathSelectorViewModel() { Label = "Third Collection Path" },
};
}
XAML:
<Window.Resources>
<DataTemplate DataType="{x:Type local:SettingsPathSelectorViewModel}">
<!-- GroupBox and Label are optional -->
<GroupBox Header="{Binding Label}">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding SettingsPath}" />
<Button
Content="..."
Command="{Binding OpenFile}"
HorizontalAlignment="Left"
MinWidth="40"
Margin="4,0,0,0"
/>
</StackPanel>
</GroupBox>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<ContentControl Content="{Binding FirstPath}" />
<ContentControl Content="{Binding SecondPath}" />
</StackPanel>
<ItemsControl
ItemsSource="{Binding SettingsPaths}"
/>
</StackPanel>
</Grid>
Here's what I mean about omitting Label and the GroupBox:
<Window.Resources>
<DataTemplate DataType="{x:Type local:SettingsPathSelectorViewModel}">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding SettingsPath}" />
<Button
Content="..."
Command="{Binding OpenFile}"
HorizontalAlignment="Left"
MinWidth="40"
Margin="4,0,0,0"
/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel Orientation="Vertical">
<Label>First Path</Label>
<ContentControl Content="{Binding FirstPath}" />
<Label>Second Path</Label>
<ContentControl Content="{Binding SecondPath}" />
</StackPanel>
</Grid>
Have xaml.cs file containing my ObservableCollection of my ViewModel. I have now implemented a command binding to a button click which invokes my function inside the viewmodel. The problem is that I do not get the item of my list in my button click function
xaml
<ItemsControl ItemsSource="{Binding ConditionList}" AlternationCount="{Binding ConditionList.Count}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Button Content="{Binding}" Command="{Binding DataContext.DeleteCondition,
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" CommandParameter="{Binding}" />
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Please note my button is in ItemControl
VM
private void DoDeleteCondition(object parameter)
{
// if (parameter != null)
// ...
}
public ICommand DeleteCondition
{
get
{
if (_DeleteCondition == null)
_DeleteCondition = new RelayCommand(o => DoDeleteCondition(o));
return _DeleteCondition;
}
}
You need to create a RelayCommand<T> where T is the Item in the ConditionList. Then you will get your parameter in the execute method.
I have a feeling that your binding is set a little backwards.
In your ItemsControl do you want to have:
the items from your collection and one command that will execute when you click on the single item
or the list of possible commands you want to execute on a single item that you have elsewhere (meaning the collection is displayed on some parent element, so you can bind to the single item somehow)?
... or maybe you have a separate command defined for every item in your collection ...? (then, how are the elements in your collection implemented?)
Depending on your answer:
1:
<ItemsControl ItemsSource="{Binding Path=MyObservableCollection}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Button Content="{Binding}"
Command="{Binding Path=DataContext.DeleteCondition, RelativeSource={RelativeSource AncestorType=AncestorWithYourViewModelAsDataContext}}"
CommandParameter="{Binding}" />
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
2:
<ItemsControl ItemsSource="{Binding Path=ConditionList}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Button Content="{Binding}"
Command="{Binding Path=MyConditionalCommand}"
CommandParameter="{BindingToTheElementOfYourCllectionThatYouWantToActUpon}" />
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
example implementation in your ViewModel:
private List<ConditionalCommand> _ConditionList;
public List<ConditionalCommand> ConditionList
{
get { return _ConditionList; }
set
{
if (_ConditionList != value)
{
_ConditionList = value;
OnPropertyChanged("ConditionList");
}
}
}
...
class ConditionalCommand
{
public ICommand MyConditionalCommand { get; set; }
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
...
this.ConditionList = new List<ConditionalCommand>();
this.ConditionList.Add(new ConditionalCommand{ MyConditionalCommand = DeleteCondition , Name="Delete"});
this.ConditionList.Add(new ConditionalCommand{ MyConditionalCommand = DeleteSpecial, Name="Delete special" });
....
private void DoDeleteCondition(object parameter)
{
// if (parameter != null)
// ...
}
public ICommand DeleteCondition
{
get
{
if (_DeleteCondition == null)
_DeleteCondition = new RelayCommand(o => DoDeleteCondition(o));
return _DeleteCondition;
}
}
// DeleteSpecial implemented in similar manner...
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));
}
});
});
}
}
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>
I am new to using RelayCommands (following Josh Smith's MVVMDemoApp) and could use some help identifying my mistake.
I have two listboxes. When an item in the first is selected and the "Add" button is pressed, the AddCommand is executed and the second list's ObservableCollection gets the selectedItem added to it.
My View:
<DockPanel >
<Border DockPanel.Dock="Bottom" Height="50" HorizontalAlignment="Left" Width="150" >
<!--Notice here that the Button was disabled until it was given a DataContext, which allowed the CanAddPN to be true-->
<Button Command="{Binding Path=AddToPartsBinCommand}" Content="Add >" />
</Border>
<UniformGrid Columns="2" Rows="1" DockPanel.Dock="Top" >
<!--ListBox 1 (PartNumbersCollection)-->
<ListBox Background="PaleGoldenrod"
ItemsSource="{Binding PnsCollection, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedPartNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding pn}">
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!--ListBox 2 (SelectedPartNumbersCollection)-->
<ListBox ItemsSource="{Binding PartsBinCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding pn}">
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UniformGrid>
</DockPanel>
My ViewModel:
//DummyDBEntities _context;
public ObservableCollection<PartNumber> _pnsCollection;
public ObservableCollection<PartNumber> _partsBinCollection;
PartNumber _selectedPN;
public ICommand _addToPartsBinCommand;
public MasterViewModel(DummyDBEntities _context)
{
_context = new DummyDBEntities();
this._pnsCollection = new ObservableCollection<PartNumber>(_context.PartNumbers);
this._partsBinCollection = new ObservableCollection<PartNumber>();
}
public ObservableCollection<PartNumber> PnsCollection
{
get { return this._pnsCollection; }
set
{
_pnsCollection = value;
OnPropertyChanged("PnsCollection");
}
}
public PartNumber SelectedPN
{
get { return this._selectedPN; }
set
{
this._selectedPN = value;
OnPropertyChanged("SelectedPN");
OnPropertyChanged("PartsBinCollection");
}
}
public ObservableCollection<PartNumber> PartsBinCollection
{
get
{
if (_partsBinCollection == null)
{
_partsBinCollection = new ObservableCollection<PartNumber>();
}
return this._partsBinCollection;
}
set
{
this._partsBinCollection = value;
OnPropertyChanged("PartsBinCollection");
}
}
public ICommand AddToPartsBinCommand
{
get
{
if (_addToPartsBinCommand == null)
_addToPartsBinCommand = new RelayCommand(() => this.AddPN(),
() => this.CanAddPN());
return this._addToPartsBinCommand;
}
}
private bool CanAddPN()
{
return true;
}
private void AddPN()
{
if (this._partsBinCollection == null)
{
this._partsBinCollection = new ObservableCollection<PartNumber>();
}
this._partsBinCollection.Add(this._selectedPN);
}
Thanks in advance!
Also: why would:
private bool CanAddPN()
{
return this._selectedPN != null;
}
leave my Add button permanently disabled? What am I not doing to let the button know that an item has been selected? This seem like it is the same missing link to my understanding of why the command isn't firing ever.
Thanks again!
You need to raise the CanExecuteChanged on your command to let the client know that it should check again to see if it can execute. Not sure about the RelayCommand but I would assume it's something along the lines of mycommand.RaiseCanExecuteChanged();
Don't forget to cast your command to a relaycommand first since you have it exposed as ICommand.
OOPS! Right after posting this after an hour of struggling I realized that in my View I was referring to the selectedItem property "SelectedPartNumber" and not "SelectedPN". This solved both problems. CanExecuteChanged is evaluated already.