How to create attached property for ItemsControl - c#

I have the following ItemsControl, as shown it has hard-coded values, I would like to shift these values into an attached property, probably an ObservableCollection or something similar.
How to create this attached property and how to bind it.
<ItemsControl Grid.Column="0" VerticalAlignment="Stretch" Name="ItemsSelected">
<sys:Double>30</sys:Double>
<sys:Double>70</sys:Double>
<sys:Double>120</sys:Double>
<sys:Double>170</sys:Double>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Fill="SlateGray" Width="18" Height="4"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Top" Value="{Binding}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
[EDIT]
So I think I have the attached property figured:
public static class ScrollBarMarkers
{
public static readonly DependencyProperty MarkersSelectedCollectionProperty =
DependencyProperty.RegisterAttached("MarkersSelectedCollection", typeof(ObservableCollection<double>), typeof(ScrollBarMarkers), new PropertyMetadata(null));
public static ObservableCollection<double> GetMarkersSelectedCollection(DependencyObject obj)
{
return (ObservableCollection<double>)obj.GetValue(MarkersSelectedCollectionProperty);
}
public static void SetMarkersSelectedCollection(ItemsControl obj, ObservableCollection<double> value)
{
obj.SetValue(MarkersSelectedCollectionProperty, value);
}
}
What I'm wondering now is the best way to get the ItemsControl object before calling the following in the selection changed behavior:
ScrollBarMarkers.SetMarkersSelectedCollection(ItemsControl, initSelected);
The style of the customized vertical scrollbar is setup in the Window.Resources
The behavior is set up on the DataGrid like so:
<DataGrid Name="GenericDataGrid">
<i:Interaction.Behaviors>
<helpers:DataGridSelectionChanged />
</i:Interaction.Behaviors>
</DataGrid>
My selection changed behavior:
public class DataGridSelectionChanged : Behavior<DataGrid>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectionChanged += DataGrid_SelectionChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.SelectionChanged -= DataGrid_SelectionChanged;
}
void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ObservableCollection<double> initSelected = new ObservableCollection<double>();
initSelected.Add(30);
initSelected.Add(60);
initSelected.Add(100);
//Just trying to figure out how best to get the ItemsControl object.
ScrollBarMarkers.SetMarkersSelectedCollection(itemsControlObj, initSelected);
}
}
Below is an example of the markers in the scrollbar, a ItemsControl has been added to the custom vertical scrollbar as per the code right at the top of the question.

If I understand your question, you want bind an ObservableCollection to ItemsControl and when the items are long the scrollbar will appear.
This solution could serve you.
[I will working with MVVM]
You can create a ObservableCollection in your code.
private ObservableCollection<int> _list = new ObservableCollection<int>();
public ObservableCollection<int> List
{
get { return _list ; }
set { _list = value; RaisePropertyChanged("List"); }
}
Now, you binding Collection to ItemsControl
<ScrollViewer HorizontalAlignment="Left" Width="254" Height="Auto" >
<ItemsControl x:Name="ItemsControlComputers" ItemsSource="{Binding List, Mode=TwoWay}" Height="Auto"
HorizontalAlignment="Left" Width="254" ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
Background="{x:Null}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Width="254">
<TextBlock Text="{Binding }" Margin="4,0,0,5" VerticalAlignment="Center">
</TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>

Went down the wrong track with this instead of creating a DependencyProperty I should have just created a plain property, however because it is UI related I did not want it with my ViewModel. So I created a class with singleton pattern in the same namespace as my behavior and other attached properties. This also means I can set the collection from any behaviors.
Here is the binding:
<ItemsControl ItemsSource="{Binding Source={x:Static helpers:MyClass.Instance}, Path=SelectedMarkers}">
Here is the class with singleton pattern
public class MyClass : INotifyPropertyChanged
{
public static ObservableCollection<double> m_selectedMarkers = new ObservableCollection<double>();
public ObservableCollection<double> SelectedMarkers
{
get
{
return m_selectedMarkers;
}
set
{
m_selectedMarkers = value;
NotifyPropertyChanged();
}
}
private static MyClass m_Instance;
public static MyClass Instance
{
get
{
if (m_Instance == null)
{
m_Instance = new MyClass();
}
return m_Instance;
}
}
private MyClass()
{
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}

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.

not work Binding ListView in WPF

i want to getimage users and show in ListView WPF/Csharp
i use of Binding for get values.
this is my code:
xaml:
<ListView ItemsSource="{Binding InstaMedia}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate >
<Image Stretch="Fill" Source="{Binding [0].Images[0].URI}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListView>
and C#:
public partial class ProfileUserPage : INotifyPropertyChanged
{
public ProfileUserPage()
{
InitializeComponent();
}
private List<InstaMedia> _instsmedia;
public List<InstaMedia> InstaMedia
{
get { return _instsmedia; }
set
{
if (_instsmedia != value)
{
_instsmedia = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyNmae = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyNmae));
}
private async void Page_LoadedAsync(object sender, RoutedEventArgs e)
{
var GetPost = await user.GetUserMediaAsync(...); //this takes about 5 seconds for get images
_instsmedia = GetPost.Value;
}
images user available in:
_instsmedia[X].Images[X].URI
but this not work and Does not show anything in my ListView. how i can fix this?
pls help me..

Windows Store App XAML Gridview not rendering data

I have the following GridView which is bound to my ViewModel
XAML Code:
<GridView SelectionMode="None" ShowsScrollingPlaceholders="False" HorizontalAlignment="Stretch" Margin="2.5 3"
ItemsSource="{Binding ProductList}" Visibility="{Binding IsGridView, Converter={StaticResource BoolToVisibility}}"
SizeChanged="SetListItemsWidth" ScrollViewer.VerticalScrollMode="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled" VerticalAlignment="Stretch">
<GridView.ItemTemplate>
<DataTemplate>
<local:ProductListControl Tag="{Binding id}" Margin="3 0 3 3" Tapped="GotoProduct"/>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
Model:
public class ProductC1View : INotifyPropertyChanged
{
public string ScreenTitle
{
get { return _ScreenTitle; }
set
{
_ScreenTitle = value;
OnPropertyChanged("ScreenTitle");
}
}
public ObservableCollection<ProductDisplay> ProductList { get; set; }
}
VIEWMODEL:
class ProductC1ViewModel : INotifyPropertyChanged
{
public ProductC1ViewModel()
{
this.View = new ProductC1View();
}
private async void ApplyFilter(object obj)
{
View.ProductList.Clear();
View.IsProductsAvailable = true;
var lstData = await _objController.GetSubCategoryDetails(View);
foreach (var product in lstData.catalogs)
View.ProductList.Add(_objHelper.FormatProductDisplayDetails(product));
}
}
The GridView is bound to an ObservableCollection. Everything works fine on Intial load or after appending new items to the collection.
But when I clear the items in the collection in case of applying filter on the data and add new items to the collection the GridView doesn't render data. The Underlying Viewmodel(ProductList) contains the data. I can bind it to a ListView and it works. Only for Gridview it doesn't render
And if I change the ItemsPanel of the gridview from ItemsWrapGrid to Stackpanel then its working, but I can't use Stackpanel since I want the list to be displayed by one item stacked next to each other like in Amazon app.
The weird case is it works in Windows 8.1 Phone app but doesn't work in Windows 10. Any help?
Kind of got a temporary fix for this, changed the Itemspanel to wrapgrid and it's working now
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid FlowDirection="LeftToRight" Orientation="Horizontal"></WrapGrid>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
You need to add RaisePropertyChanged (require Mvvmlight) in ProductList setter.
private ObservableCollection<ProductDisplay> _productList;
public ObservableCollection<ProductDisplay> ProductList
{
get
{
return _productList;
}
set
{
_productList = value;
RaisePropertyChanged(()=>ProductList);
}
}
Or staying with just INotifyPropertyChanged you need to implement the PropertyChangedEventHandler ( like in this tutorial):
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private ObservableCollection<ProductDisplay> _productList;
public ObservableCollection<ProductDisplay> ProductList
{
get
{
return _productList;
}
set
{
_productList = value;
NotifyPropertyChanged();
}
}
And you don't necessary need a GridView to display tiles of pictures :
<ListBox ItemsSource="{Binding AllDesignSheetsInDB}" SelectedItem="{Binding CurrentDesignSheet}" BorderBrush="LightGray" BorderThickness="2" ScrollViewer.HorizontalScrollBarVisibility="Disabled" >
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
<ListBox.ItemTemplate>
<DataTemplate>
<local:ProductListControl Tag="{Binding id}" Margin="3 0 3 3" Tapped="GotoProduct"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

custom control binding with ObservableCollection

I have custom control MyGrid
public class MyGrid : Canvas
{
//...
ObservableCollection<object> items = new ObservableCollection<object>();
public ObservableCollection<object> Items
{
get { return items; }
set {
items = value;
UpdateValues();
UpdateGrid();
}
}
//..
}
And I want Items to be bindable from XAML code:
<local:MyGrid Items="{Binding Numbers}" />
Where Numbers is ObservableCollection (which works fine, I can use it to bind to default controls).
I've tried to define Items as DependencyProperty, but it is static and I need to use more than one control on page with different sources of data, so using static items won't work. The code above doesn't work as well. InitializeComponent() throws an exception: Failed to assign to property 'App.MyGrid.Items'. [Line: 27 Position: 114]. How can I make it work?
As your MyGrid extends from Canvas (Which is long last also a DependecyObject) you could implement the Dependency property inside the MyGrid.
You could then also implement it with a PropertyChangedCallback which would allow you to register/unregister to the event itself, where you could then update your grid / values
So you could change the MyGrid like this:
public class MyGrid : Canvas
{
protected static PropertyChangedCallback ItemsPropertyChangedCallback = new PropertyChangedCallback(ItemsPropertyChanged);
public static DependencyProperty ItemsProperty = DependencyProperty.RegisterAttached("Items", typeof(INotifyCollectionChanged), typeof(MyGrid), new PropertyMetadata(null, ItemsPropertyChangedCallback));
private static void ItemsPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MyGrid thisGrid = (MyGrid)sender;
if (thisGrid == null)
{
return;
}
thisGrid.UnregisterItems(e.OldValue as INotifyCollectionChanged);
thisGrid.RegisterItems(e.NewValue as INotifyCollectionChanged);
thisGrid.Refresh();
}
public INotifyCollectionChanged Items
{
get
{
return (INotifyCollectionChanged)GetValue(ItemsProperty);
}
set
{
SetValue(ItemsProperty, value);
}
}
protected void UnregisterItems(INotifyCollectionChanged items)
{
if (items == null)
{
return;
}
items.CollectionChanged -= ItemsChanged;
}
protected void RegisterItems(INotifyCollectionChanged items)
{
if (items == null)
{
return;
}
items.CollectionChanged += ItemsChanged;
}
protected virtual void UpdateValues()
{
System.Diagnostics.Debug.WriteLine("Updating values");
}
protected virtual void UpdateGrid()
{
System.Diagnostics.Debug.WriteLine("Updating grid");
}
public void Refresh()
{
UpdateValues();
UpdateGrid();
}
protected virtual void ItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Refresh();
}
public MyGrid()
{
}
}
and in Xaml you could then later on Bind to the Items property. When the Items property is changed with another collection, it will unregister from the changed event of the last object (if there was one), and then register to the new object (if there is one). Afterwards, it will call the Refresh method of your class (that then calls the UpdateValues / UpdateGrid methods)
I also partly agree with #user3248647 that you should take advantage of Binding and ContentTemplates when you can, but if you cannot use that, you could get your DependencyProperty reacting at least like this.
And yes, the DependencyProperty is static on the class, but the property itself is always implemented inside the class. When using the PropertyChangedCallback, just cast the sender back to "MyGrid" and then you can change the instance members :)
May be this will help you.
<ItemsControl ItemsSource="{Binding items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,5,5,0" VerticalAlignment="Top">
<TextBlock TextAlignment="Right" FontWeight="Bold" Text="{Binding yourVariable}" Height="16"/>
<TextBlock TextAlignment="Right" Text="{Binding yourVariable1}" FontSize="26"/>
<TextBlock TextAlignment="Right" Text="{Binding yourVariable2}" FontSize="10" Foreground="DarkGray"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer Padding="{TemplateBinding Padding}" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel>
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
This may be not actually what you looking for. But I think this can help you.

ListViewItem IsSelected Binding - Works for WPF, but not for WinRT

I'm trying to bind the IsSelected property of a ListViewItem to a property in a ViewModel. It works fine in WPF, but in Windows RT the IsSelected property is never getting set.
public class Item : INotifyPropertyChanged
{
private readonly string name;
private bool isSelected;
public event PropertyChangedEventHandler PropertyChanged;
public bool IsSelected
{
get { return isSelected; }
set { isSelected = value; RaisePropertyChanged("IsSelected"); }
}
public string Name { get { return name; } }
public Item(string name)
{
this.name = name;
}
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ViewModel
{
private readonly ObservableCollection<Item> items = new ObservableCollection<Item>(Enumerable.Range(0, 10).Select(p => new Item(p.ToString())));
public ObservableCollection<Item> Items { get { return items; } }
}
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
DataContext = new ViewModel();
}
}
xaml:
<StackPanel Orientation="Horizontal">
<ListView ItemsSource="{Binding Path=Items}" SelectionMode="Multiple">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</StackPanel>
I'm able to click on the items on the screen, but the IsSelected property isn't propagating to the ViewModel. Any ideas why?
A good and easy way to do this is to subclass ListView
public class MyListView : ListView
{
protected override void PrepareContainerForItemOverride(Windows.UI.Xaml.DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
// ...
ListViewItem listItem = element as ListViewItem;
Binding binding = new Binding();
binding.Mode = BindingMode.TwoWay;
binding.Source = item;
binding.Path = new PropertyPath("Selected");
listItem.SetBinding(ListViewItem.IsSelectedProperty, binding);
}
}
Alternatively, it seems you can also do it with WinRT XAML Toolkit.
<ListView
x:Name="lv"
Grid.Row="1"
Grid.Column="1"
SelectionMode="Multiple"
HorizontalAlignment="Left"
Width="500">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock
Extensions:ListViewItemExtensions.IsSelected="{Binding IsSelected}"
Extensions:ListViewItemExtensions.IsEnabled="{Binding IsEnabled}"
Text="{Binding Text}"
Margin="15,5"
FontSize="36" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Personally, I used the first approach because it's more flexible and I needed to bind a few Automation Properties.
Credits to ForInfo and ehuna:
http://social.msdn.microsoft.com/Forums/windowsapps/en-US/9a0adf35-fdad-4419-9a34-a9dac052a2e3/listviewitemisselected-data-binding-in-style-setter-is-not-working
WinRT doesn't support bindings in setters at all as of Windows 8.0. Bing for workarounds.

Categories

Resources