Can't access datacontext of parent - c#

Basically what i have is a ListBox with ContextMenu
<ListBox Margin="2,0,0,0" Grid.Row="1" ItemsSource="{Binding MyCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Style="{StaticResource NoVisualButton }" Tag="{Binding ID}" Width="430" toolkit:TiltEffect.IsTiltEnabled="True" Margin="0,0,0,12" Click="OnSelectWorkOutItemClick">
<StackPanel>
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
<toolkit:MenuItem Header="delete" Tag="{Binding ID}" Click="onContextMenuDeleteItemClick" IsEnabled="{Binding IsDeleteOptionEnable, ElementName=LayoutRoot}"/>
<toolkit:MenuItem Header="edit" Tag="{Binding ID}" Click="onContextMenuItemEditClick" />
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
...
</StackPanel>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
So if MyCollection has only one item, i have to disable delete MenuItem.
My model has a property
public bool IsDeleteOptionEnable
{
get
{
return MyCollection.Count() >= 2;
}
}
In the page i am setting the DataContext like:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (IsDataLoaded)
{
this.DataContext =MyModel;
}
}
The listbox is getting populated, but i can't disable "delete" MenuItem. What am i doing wrong?

Since the IsDeleteOptionEnable is a regular property, your view won't get notified when the property is changed. On options would be implementing INotifyPropertyChanged in your model (actually that should be ViewModel in an MVVM pattern) and calling the PropertyChanged event whenever items in your collection gets changed.
class YourModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
..
..
public YourModel() {
this.MyCollection = ...;
this.MyCollection.CollectionChanged += MyCollection_CollectionChanged;
}
public bool IsDeleteOptionEnable {
get {
return MyCollection.Count() >= 2;
}
}
private void MyCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
this.OnPropertyChanged("IsDeleteOptionEnable");
}
private void OnPropertyChanged(string name = null) {
if (this.PropertyChanged != null) {
PropertyChangedEventArgs ea = new PropertyChangedEventArgs(name);
this.PropertyChanged(this, ea);
}
}
}
Now when an item get removed or added to the collection, the model raises and PropertyChanged event so that the view will be aware that the IsDeleteOptionEnable property is (actually might) changed, and the enabled state of the button gets updated.

Try
IsEnabled="{Binding DataContext.IsDeleteOptionEnable, ElementName=LayoutRoot}"

As DataSource you need to use ObservableCollection. Then you need to implement INotifyPropertyChanged -interface in the class which contains the binded Property.
Example Class:
// Example of binded object
public class MyItem: INotifyPropertyChanged {
// Binded Property
private String itemIsVisible = "Yes";
public String ItemIsVisible{
get { return itemIsVisible; }
set {
itemIsVisible = value;
// This ensures the updating
OnPropertyChanged("ItemIsVisible");
}
}
protected void OnPropertyChanged(string name) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Example XAML:
<TextBlock Text="{Binding ItemIsVisible}" />

Related

Pass ObservableCollection<> type as dependency property

I am trying to create a multi-select Combobox Custom control, This custom control should expose a dependency property called DropDownDataSource through which the user of the control can decide what day should bound to ComboBox. My code looks like this:
MainPage.Xaml
<Grid>
<local:CustomComboBox x:Name="customcb" DropDownDataSource="{x:Bind DropDownDataSource, Mode=OneWay}" Loaded="CustomControl_Loaded"> </local:CustomComboBox>
</Grid>
MainPage.Xaml.cs
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
private ObservableCollection<Item> _dropDownDataSource;
public ObservableCollection<Item> DropDownDataSource
{
get => _dropDownDataSource;
set
{
_dropDownDataSource = value;
OnPropertyChanged();
}
}
public MainPage()
{
this.InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private void CustomControl_Loaded(object sender, RoutedEventArgs e)
{
var Items = new ObservableCollection<Item>(Enumerable.Range(1, 10)
.Select(x => new Item
{
Text = string.Format("Item {0}", x),
IsChecked = x == 40 ? true : false
}));
DropDownDataSource = Items;
}
}
Models
public class Item : BindableBase
{
public string Text { get; set; }
bool _IsChecked = default;
public bool IsChecked { get { return _IsChecked; } set { SetProperty(ref _IsChecked, value); } }
}
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T storage, T value,
[System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (!object.Equals(storage, value))
{
storage = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
protected void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
CustomUserControl XAML
<Grid x:Name="GrdMainContainer">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBox Width="200" FontSize="24" Text="{Binding Header, Mode=TwoWay}"
IsReadOnly="True" TextWrapping="Wrap" MaxHeight="200" />
<ScrollViewer VerticalScrollBarVisibility="Auto" MaxHeight="200" Width="200" Background="White">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}"
FontSize="24"
Foreground="Black"
IsChecked="{Binding IsChecked, Mode=TwoWay}"
IsThreeState="False" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</StackPanel>
</Grid>
CustomUserControl Cs file
public sealed partial class CustomComboBox : UserControl
{
public CustomComboBox()
{
this.InitializeComponent();
}
public ObservableCollection<Item> DropDownDataSource
{
get { return (ObservableCollection<Item>)GetValue(DropDownDataSourceProperty); }
set { SetValue(DropDownDataSourceProperty, value); }
}
public static readonly DependencyProperty DropDownDataSourceProperty =
DependencyProperty.Register("DropDownDataSource", typeof(ObservableCollection<Item>), typeof(CustomComboBox), new PropertyMetadata("", HasDropDownItemUpdated));
private static void HasDropDownItemUpdated(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CustomComboBox ucrcntrl)
{
var grd = UIElementExtensions.FindControl<Grid>(ucrcntrl, "GrdMainContainer");
grd.DataContext = ucrcntrl.DropDownDataSource as ObservableCollection<Item>;
}
}
}
All looks good to me, but for some reason, Dropdown is coming empty. Instead of the dependency property, If I assign a view model directly to the Control it works fine. But in my condition, it is mandatory that I have properties like DataSource,SelectedIndex, etc on the user control for the end-user to use. Can anyone point out what is going wrong here?
Here, I have attached a copy of my complete code.
I downloaded your sample code, the problem should be in the binding.
<ItemsControl ItemsSource="{Binding Items}">
This way of writing is not recommended. In the ObservableCollection, Items is a protected property and is not suitable as a binding property.
You can try to bind dependency property directly in ItemsControl:
<ItemsControl ItemsSource="{x:Bind DropDownDataSource,Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="local:Item">
<CheckBox IsChecked="{x:Bind IsChecked, Mode=TwoWay}"
IsThreeState="False" >
<TextBlock Text="{x:Bind Text}" Foreground="Black" FontSize="24"/>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In addition, you may have noticed that I modified the style of CheckBox and rewritten the content to TextBlock, because in the default style of CheckBox, Foreground is not bound to the internal ContentPresenter.
Thanks.

PropertyChanged Event handler is always null also with OneWay specification

i created a simple ListView in XAML which should bind to an ObservablaCollection:
<PivotItem x:Uid="pvItemMusic" Header="Music">
<StackPanel>
<TextBlock Name="tbSelectMusicHeader" Text="Select directories that should be included into your library" FontSize="18" Margin="20"></TextBlock>
<Button Name="btnSelectSourcePath" Content="Add path" Margin="30,10,0,10" Click="btnSelectSourcePath_Click"></Button>
<ListView Name="lvPathConfiguration" DataContext="{StaticResource configurationVM}" ItemsSource="{Binding MusicBasePathList, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate>
<RelativePanel>
<TextBlock Name="tbPath" Text="{Binding Mode=OneWay}" RelativePanel.AlignTopWithPanel="True" VerticalAlignment="Center" Width="400" Margin="20"></TextBlock>
<Button Name="btnRemovePath" x:Uid="btnRemovePath" Content="Remove" RelativePanel.RightOf="tbPath" Margin="10" Height="48"></Button>
</RelativePanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</PivotItem>
The namespace of my ViewModel is imported by
xmlns:applicationVM="using:Crankdesk.CrankHouseControl.ViewModel.Application"
and the Page Resource i added my ViewModel:
<Page.Resources>
<applicationVM:ConfigurationViewModel x:Key="configurationVM"></applicationVM:ConfigurationViewModel>
</Page.Resources>
btnSelectSourcePath should add a path to the list of source pathes that are stored in ViewModel, which will be done in CodeBehind:
private async void btnSelectSourcePath_Click(object sender, RoutedEventArgs e)
{
FolderPicker picker = new Windows.Storage.Pickers.FolderPicker();
picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.MusicLibrary;
picker.FileTypeFilter.Add("*");
StorageFolder folder = await picker.PickSingleFolderAsync();
if (folder != null)
{
// Save path to configuration
App.ConfigurationViewModel.MusicBasePathList.Add(folder.Path);
}
}
In ViewModel the "INotifyPropertyChanged" Event is used and i use the "CollectionChanged" Event of my ObersableCollection to fire the PropertyChanged Event. When i add a path in debug mode, the RaisePropertyChanged Method will be executed, but the "handler" property is always NULL.
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Here is my whole ViewModel:
namespace Crankdesk.CrankHouseControl.ViewModel.Application
{
public class ConfigurationViewModel : INotifyPropertyChanged
{
private ObservableCollection<string> _musicBasePathList;
public ObservableCollection<string> MusicBasePathList
{
get
{
return _musicBasePathList;
}
set
{
_musicBasePathList = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ConfigurationViewModel()
{
_musicBasePathList = new ObservableCollection<string>();
_musicBasePathList.CollectionChanged += _musicBasePathList_CollectionChanged;
}
private void _musicBasePathList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged(nameof(MusicBasePathList));
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
What do i wrong here? I know i ask this question the 34th time here, but i didn't find a solution. In most cases they forgot to specify OneWay or TwoWay, but that's not the case here.
Thanks in advance....
Dave
You have at minimum two instances of ConfigurationViewModel in your application.
App.ConfigurationViewModel
defined in the page ressources as configurationVM
The view is bound to the 2. instance and in code behind you modify the 1. instance.

Refresh View When Binding Object Changes

I have a Textbox in WPF which has its "Text" Property bound to a string "EmployeeSource.ID" with Mode=TwoWay. My problem is that when i change the EmployeeSource object, the binding does not work. What is wrong in my approach?
XAML
<TextBox x:Name="NameTextBox" Margin="5,5,10,5" TextWrapping="Wrap"
Text="{Binding SelectedEmployee.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1" Grid.Column="1" />
Code Behind
private Employee _selectedEmployee;
public Employee SelectedEmployee
{
get { return _selectedEmployee; }
set
{
_selectedEmployee = value;
UpdateTextBoxes();
}
}
private void UpdateTextBoxes()
{
NameTextBox.Text = SelectedEmployee?.Name;
}
Please try the code below. You need to implement the INotifyPropertyChanged interface inorder to achieve data binding in WPF. This is the basic concept of WPF data binding and MVVM pattern. This should work for you.
Code behind:
public class YourClassName : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private Employee _selectedEmployee;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
// The constructor is private to enforce the factory pattern.
private YourClassName()
{
_selectedEmployee = new Employee();
}
public Employee selectedEmployee
{
get
{
return this._selectedEmployee;
}
set
{
if (value != this._selectedEmployee)
{
this._selectedEmployee = value;
NotifyPropertyChanged("selectedEmployee");
}
}
}
}
XAML :
<TextBox x:Name="NameTextBox" Margin="5,5,10,5" TextWrapping="Wrap"
Text="{Binding selectedEmployee.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1" Grid.Column="1" />

Changing ObservableCollection at runtime for binding

I am generating Grid for every item from my ObservableCollection. Now I want to be able to change the source collection at runtime and I am not sure what needs to be done.
Here is my XAML:
<Window.Resources>
<c:GraphicsList x:Key="GraphicsData" />
</Window.Resources>
...
...
<ItemsControl x:Name="icGraphics" ItemsSource="{Binding Source={StaticResource GraphicsData}}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Tag="{Binding id}" Margin="15,0,15,15">
<Label Grid.Row="0" Content="{Binding name}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And C#:
myCollection1 = this.FindResource("GraphicsData") as GraphicsList;
myCollection1:
public class GraphicsList : ObservableCollection<Graphics>
{
public GraphicsList()
{
}
}
Graphics class:
class Graphics: INotifyPropertyChanged
{
// some properties not important
}
Its a simplyfied version of my code, but it works, I basically a want to change the source collection myCollection1 to myCollection2 (which is same class just different list). How do I do this?
You can Add or Remove items from collection as below
var dresource = this.Resources["GraphicsData"] as GraphicsList;
dresource.Add(new Graphics() { Name = "New Entry" });
But with StaticResource you can't assign new Collection to one in ResourceDictionary.
Ideally you should be using ViewModel and bind Collection if you want to assign completely new collection.
Your mainwindow class or viewmodel should implement INotifyPropertyChanged interface
Sample code
public partial class MainWindow : Window, INotifyPropertyChanged
{
private GraphicsList _graphicsData;
public MainWindow()
{
InitializeComponent();
DataContext = this;
this.Loaded += MainWindow_Loaded;
}
public GraphicsList GraphicsData
{
get { return _graphicsData; }
set
{
if (Equals(value, _graphicsData)) return;
_graphicsData = value;
OnPropertyChanged("GraphicsData");
}
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
//var resource = this.Resources["GraphicsData"] as GraphicsList;
var resource = new GraphicsList();
resource.Add(new Graphics(){Name = "Some new Collection of data"});
this.GraphicsData = resource;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And Your Xaml
<Grid>
<ListBox ItemsSource="{Binding GraphicsData}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I hope this will help

Access ListBoxItem children

I have a ListBox which gets populated dynamically by my own class. This is an example of my listbox:
<ListBox x:Name="mylistbox" SelectionChanged="timelinelistbox_SelectionChanged_1">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding userid}" Visibility="Collapsed" />
<TextBlock Text="{Binding postid}" Visibility="Collapsed" />
<Image Source="{Binding thumbnailurl}" />
<TextBlock Text="{Binding username}" />
<TextBlock Text="{Binding description}" />
<Image Source="{Binding avatar}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When the SelectedItemChanged event of the ListBox gets triggered I get my ListBoxItem.
But now I want to alter the children in that ListBoxItem... But I can't seem to access the children of the ListBoxItem?
I tried:
private void timelinelistbox_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
{
//Get the data object that represents the current selected item
MyOwnClass data = (sender as ListBox).SelectedItem as MyOwnClass;
//Get the selected ListBoxItem container instance
ListBoxItem selectedItem = this.timelinelistbox.ItemContainerGenerator.ContainerFromItem(data) as ListBoxItem;
// change username and display
data.username = "ChangedUsername";
selectedItem.Content = data;
}
But the username doesn't change...
You don't have to change back Content of selected ListBoxItem. MyOwnClass is a class, I assume, and therefore reference type so changing username in one instance will have effect in all references to the same object. Your MyOwnClass should implement INotifyPropertyChanged interface (MSDN) and raise PropertyChanged event each time property changes. Like that you notify all bound controls that the property has changed and need refreshing:
public class MyOwnClass : INotifyPropertyChanged
{
private string _username;
public string username
{
get { return _username ; }
set
{
if (_userName == value) return;
_userName = value;
NotifyPropertyChanged("username");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
and then it will be enough if you do:
private void timelinelistbox_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
{
((sender as ListBox).SelectedItem as MyOwnClass).username = "ChangedUsername";
}

Categories

Resources