I have two user controls
parent Orders that creates a list of anonymous objects
child Table that shows this list in a DataGrid
Parent XAML and CS
public partial class OrdersControl : UserControl
{
public static readonly DependencyProperty OrderItemsProp = DependencyProperty.Register(
"OrderItems",
typeof(IEnumerable<dynamic>),
typeof(TableControl),
new PropertyMetadata(null));
public IEnumerable<dynamic> OrderItems
{
get { return (IEnumerable<dynamic>)GetValue(OrderItemsProp); }
set { SetValue(OrderItemsProp, value); }
}
}
...
<UserControl x:Name="OrdersContainer">
<ctrl:TableControl x:Name="ChildTable" Items="{ Binding OrderItems, ElementName=OrdersContainer }" />
</UserControl>
Child XAML and CS
public partial class TableControl : UserControl
{
public static readonly DependencyProperty ItemsProp = DependencyProperty.Register(
"Items",
typeof(IEnumerable<dynamic>),
typeof(TableControl),
new PropertyMetadata(null));
public IEnumerable<dynamic> Items
{
get { return (IEnumerable<dynamic>)GetValue(ItemsProp); }
set { SetValue(ItemsProp, value); }
}
}
...
<UserControl x:Name="TableContainer">
<DataGrid
ColumnWidth="*"
x:Name="DataItems"
IsReadOnly="True"
CanUserAddRows="False"
VerticalAlignment="Top"
GridLinesVisibility="Vertical"
AutoGenerateColumns="True"
CanUserDeleteRows="False"
HeadersVisibility="Column"
ItemsSource="{ Binding Items, ElementName=TableContainer }"
MinColumnWidth="0">
</DataGrid>
</UserControl>
Scenarios
If I initialize parent property OrderItems in the parent constructor, it works fine, so data binding works
public OrdersControl
{
OrderItems = new[]
{
new { Name = "A", Price = 1 },
new { Name = "B", Price = 2 }
};
InitializeComponent();
}
If I subscribe to updates from IObservable and update OrderItems property, I get a UI thread exception
If I wrap initialization of OrderItems with Dispatcher.Invoke, nothing happens, child DataGrid stays empty
accounts
.Select(account => account.Orders.Stream)
.Merge()
.Subscribe(message =>
{
Application
.Current
.Dispatcher
.BeginInvoke(new Action(() => OrderItems = items));
});
Why does child control is not initialized if initialization is inside Dispatcher call?
For now, I decided to set child DataGrid.ItemsSource property directly from parent code-behind. Still can't explain why setting parent dependency property doesn't initialize child dependency property with Binding in XAML.
Parent CS
accounts
.Select(account => account.Orders.Stream)
.Merge()
.Subscribe(message =>
{
Application
.Current
.Dispatcher
.BeginInvoke(new Action(() => ChildTable.DataItems.ItemsSource = items));
});
Related
I have a view which wraps a TreeView, called MbiTreeView. I want to get the selected item from the (wrapped) tree view in the view model.
The 'parent' user control which uses this custom user control:
<UserControl [...]>
<views:MbiTreeView
Grid.Row="0"
cal:Bind.Model="{Binding TreeViewModel}"
SelectedItem="{Binding SelectedItem}">
</views:MbiTreeView>
</UserControl>
The parent user control is bound to this view model:
internal sealed class SomeViewModel : PropertyChangedBase
{
public object SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
NotifyOfPropertyChange(() => SelectedItem);
}
}
public IMbiTreeViewModel TreeViewModel { get; }
public SomeViewModel(
IMbiTreeViewModel treeViewModel)
{
TreeViewModel = treeViewModel;
}
}
The MbiTreeView user control is rather straight forward. It subscribes to the selection changed event, and defines a few templates (not relevant for this question, so left them out in the question)
<TreeView ItemsSource="{Binding Items}" SelectedItemChanged="TreeView_OnSelectedItemChanged">
iew.ItemContainerStyle>
The code behind declares the dependency property:
public partial class MbiTreeView
{
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register(
nameof(SelectedItem),
typeof(object),
typeof(MbiTreeView),
null);
public object SelectedItem
{
get => GetValue(SelectedItemProperty);
set => SetValue(SelectedItemProperty, value);
}
public MbiTreeView()
{
InitializeComponent();
}
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItem = e.NewValue;
}
}
when I start the application, I can navigate through the tree view items. When I click on a treeview node, then the OnSelectedItemChanged event fires (I get into my breakpoint there). So everything goes fine up and until setting the value in the dependency property SelectedItem.
Then I would expect that the xaml binding gets notified, and updates the view model. But that never happens.
I am getting nowhere with this, help is greatly appreciated.
The SelectedItem Binding should be TwoWay:
<views:MbiTreeView ...
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"/>
You could declare the property like shown below to make to bind TwoWay by default.
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(
nameof(SelectedItem),
typeof(object),
typeof(MbiTreeView),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
I have UserControl, lets call it as CustomDataGrid, that contains DataGrid. Remained content doesn't matter. SelectedItem property of DataGrid must be SelectedItem property of CustomDataGrid. And I wanna be able to use Binding with this property, cause I use MVVM pattern. So I have to declare SelectedItem as DependencyProperty in CustomDataGrid. But I have no ideas haw can I make it properly...
This is how DepedencyProperty-s is declared usually:
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register(
"SelectedItem", typeof(Object), typeof(CustomDataGrid),
new FrameworkPropertyMetadata(default(Object), SelectedItemPropertyCallback)
{
BindsTwoWayByDefault = true,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
// Optionally
private static void SelectedItemPropertyCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
// dataGrid - `DataGrid` nested in `UserControl`
((CustomDataGrid)obj).dataGrid.SelectedItem = e.NewValue;
}
// Obviously, it has no any link with nested `dataGrid`. This is the problem.
public Object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
So, how can I declare SelectedItem property correctly?
You could leverage the binding framework for wiring such properties from underlying objects to outer containers
example assuming CustomDataGrid as UserControl
class CustomDataGrid : UserControl
{
public CustomDataGrid()
{
Binding b = new Binding("SelectedItem");
b.Source = this;
b.Mode = BindingMode.TwoWay;
dataGrid.SetBinding(DataGrid.SelectedItemProperty, b);
}
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(CustomDataGrid), new PropertyMetadata(null));
}
I have created a property called SelectedItem in CustomDataGrid and set a two way binding to SelectedItem of the actual dataGrid inside.
so this will wire up these properties and will propagate any changes to and fro.
XAML solution!
Use this DependencyProperty:
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(CustomDataGrid ), new FrameworkPropertyMetadata(null)
{
BindsTwoWayByDefault = true,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
Then make your outer CustomDataGrid UserControl XAML look like this:
<UserControl x:Class="CustomDataGrid">
<DataGrid ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type CustomDataGrid}}}"
SelectedItem="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type CustomDataGrid}}}">
</DataGrid>
</UserControl>
You can then use the CustomDataGrid control the same way as the DataGrid control when binding ItemsSource and SelectedItem to your view model.
I have a custom class which a usercontrol has implemented as a dependency property in it's code behind.
public partial class HandControl
{
public HandControl()
{
InitializeComponent();
}
public Seat Seat
{
get
{
return (Seat)GetValue(SeatProperty);
}
set
{
SetValue(SeatProperty, value);
}
}
public static readonly DependencyProperty SeatProperty = DependencyProperty.Register("Seat", typeof(Seat), typeof(HandControl), new PropertyMetadata(null));
}
In my case I've bound the name property in that class to a label inside the usercontrols xaml.
<Label Content="{Binding Seat.Player.Name, RelativeSource={RelativeSource AncestorType={x:Type controls:HandControl}}}"/>
The view model of my window contains the property SeatTl and the xaml is binding to it:
public Seat SeatTr
{
get { return _seatTr; }
private set
{
_seatTr = value;
OnPropertyChanged();
}
}
<customControls:HandControl Grid.Row="1"
Grid.Column="3"
Seat="{Binding SeatTr}" />
However, when I change my class content (the name property) and manually raise OnPropertyChanged in my viewmodel (not the usercontrol), the label is not updated and still has the same content.
private void OnSeatChanged(Player player, SeatPosition seatPosition)
{
//... doing the changes ...\\
OnPropertyChanged("SeatTr");
}
Whats my problem? Anyone got a clue?
I think u should raise OnPropertyChanged for Seat.Player.Name property as It is being chaged.
i want to bind a DataGrid SelectedItem inside a user control to a DependencyProperty
and this is my code:
in the user control(DataGridControl):
public static readonly DependencyProperty DataGridSelectedItemProperty
= DependencyProperty.Register(
"DataGridSelectedItem"
, typeof(object)
, typeof(DataGridSelectorControl)
, new UIPropertyMetadata(null));
public object DataGridSelectedItem
{
get { return (object)GetValue(DataGridSelectedItemProperty); }
set { SetValue(DataGridSelectedItemProperty, value); }
}
<DataGrid ItemsSource="{Binding Source={StaticResource theSource}}"
SelectedItem="{Binding ElementName=DataGridControl,Path=DataGridSelectedItem,UpdateSourceTrigger=PropertyChanged}" />
and in the viewmodel:
object projectSelectedItem;
public object ProjectSelectedItem
{
get
{
return projectSelectedItem;
}
set
{
projectSelectedItem = value;
RaisePropertyChanged("ProjectSelectedItem");
}
}
and in view:
<MvvmCommonControl:DataGridControl DataGridSelectedItem="{Binding ProjectSelectedItem}" DataGridDataCollection="{Binding DataCollection}"/>
but it dosen't work!!
You have the following in your user control:
SelectedItem="{Binding ElementName=DataGridControl,
But is you user control named DataGridControl?
<UserControl
...
x:Name="DataGridControl">
In Xaml use the below code
<DataGrid ItemsSource="{Binding Path=Person,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False" SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
In ViewModel Create property for selected item.Here Customer is class which is having properties like Address,Name,OrderID.
private Customer selectedItem = new Customer();
public Customer SelectedItem
{
get
{ return selectedItem; }
set
{
selectedItem = value;
InvokePropertyChanged("SelectedItem");
}
}
Create one class to define dependency property
class DataGridSelectedItemBehaviour:DependencyObject
{
public static readonly DependencyProperty SelectedItemProperty
= DependencyProperty.Register(
"SelectedItem"
, typeof(object)
, typeof(CustomerViewModel)
, new UIPropertyMetadata(null));
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
}
Tried may approches to displaying a "no data" if there are no items in listbox. Since I'm on wp7 and using silverlight I can't use DataTriggers, so I've created a control to have it behave consistently across the whole app. BUT I if you set the breakpoint for the set method - it's not being called at all!
The control class
public class EmptyListBox : ListBox
{
public new IEnumerable ItemsSource
{
get
{
return base.ItemsSource;
}
set
{
// never here
base.ItemsSource = value;
ItemsSourceChanged();
}
}
protected virtual void ItemsSourceChanged()
{
bool noItems = Items.Count == 0;
if (noItems)
{
if (Parent is System.Windows.Controls.Panel)
{
var p = Parent as Panel;
TextBlock noData = new TextBlock();
noData.Text = "No data";
noData.HorizontalAlignment = HorizontalAlignment;
noData.Width = Width;
noData.Height = Height;
noData.Margin = Margin;
p.Children.Add(noData);
Visibility = System.Windows.Visibility.Collapsed;
}
}
}
}
This is xaml
<my:EmptyListBox ItemsSource="{Binding Path=MyData}" Name="myListBox">
<my:EmptyListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=name}" />
</DataTemplate>
</my:EmptyListBox.ItemTemplate>
</my:EmptyListBox>
Codebehind:
ClientModel ClientInfo { get; set; }
public ClientView()
{
ClientInfo = new ClientModel();
ClientInfo.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(DataReady);
DataContext = ClientInfo
}
ClientModel class:
public class ClientModel : INotifyPropertyChanged
{
MyData _myData;
public MyData MyData
{
get
{
return _myData;
}
set
{
_myData = value;
NotifyPropertyChanged("MyData");
}
}
public void GetClient(int id)
{
// fetch the network for data
}
}
LINK TO SOLUTION .ZIP THAT SHOWS THE PROBLEM
http://rapidshare.com/files/455900509/WindowsPhoneDataBoundApplication1.zip
Your new ItemSource should be a DependencyProperty.
Anything that is working with Bindings have to be a DependencyProperty.
Simply make it a DependencyProperty.
I think the solution I'd go for is something like this:
Define a new visual state group ItemsStates and two visual states: NoItems and HasItems.
In the ControlTemplate for your custom listbox, add the visual tree for your "no data" state.
In the NoItems state, set the Visibility of your "no data" elements to Visible and set the Visibility of the default ItemsPresenter to Collapsed.
In the HasItems state, swap the Visibility of these elements.
In an OnApplyTemplate override switch to the Empty state by default: VisualStateManager.GoToState(this, "Empty", true);
In an OnItemsChanged override, check whether the items source is empty and use VisualStateManager to switch between these states accordingly.
That should work :)
Create ItemsSource as a DependencyProperty.
Example:
public IEnumerable ItemsSource
{
get { return (IEnumerable)base.GetValue(ItemsSourceProperty); }
set { base.SetValue(ItemsSourceProperty, value); }
}
public static DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
"ItemsSource",
typeof(IEnumerable),
typeof(EmptyListBox),
new PropertyMetadata(null));
try to implement the INotifyPropertyChanged interface and use for ItemsSource an ObservableCollection. In the Setter of your Property just call the OnPropertyChanged method.
Maybe this will help.
Try adding Mode=TwoWay to the ItemsSource binding:
<my:EmptyListBox ItemsSource="{Binding Path=MyData, Mode=TwoWay}" Name="myListBox">
<my:EmptyListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=name}" />
</DataTemplate>
</my:EmptyListBox.ItemTemplate>
</my:EmptyListBox>