I know this topic has been discussed, but not by me yet. As I have seen on other examples about this issue, I am trying to create some basic custom DataPager UserControl. So that I did the following :
XAML:
<ComboBox Name="Size" ItemsSource="{Binding PageSourceSize}"
SelectedValue="{Binding PageSizePager}" />
With the following C#:
ObservableCollection<int> _PageSourceSize;
public ObservableCollection<int> PageSourceSize
{
get { return _PageSourceSize; }
set
{
_PageSourceSize = value;
RaisePropertyChanged("PageSourceSize");
}
}
public MyDataPager()
{
DataContext = this;
PageSizePager = 10;
PageSourceSize = new ObservableCollection<int>() { 10, 20, 50,100 };
}
public int PageSizePager
{
get { return (int)GetValue(PageSizePagerProperty); }
set { SetValue(PageSizePagerProperty, value); }
}
public static readonly DependencyProperty PageSizePagerProperty =
DependencyProperty.Register("PageSizePager", typeof(int), typeof(MyDataPager), new PropertyMetadata(10));
From here I intend to use my pager in a main UserControl :
<local:MyDataPager PageSizePager="20" x:Name="MyDataPager1" />
This works fine, but I would have liked to get the value from my viewModel using:
<local:MyDataPager PageSizePager="{Binding Path=PageSize,Mode=TwoWay}" x:Name="MyDataPager1" />
And the view model:
public int PageSize
{
get { return (int)GetValue(PageSizeProperty); }
set { SetValue(PageSizeProperty, value); }
}
public static readonly DependencyProperty PageSizeProperty =
DependencyProperty.Register("PageSize", typeof(int), typeof(ViewSchedeConsuntiviViewModel), new PropertyMetadata(10));
public MyViewModel()
{
PageIndex = 1;
PageSize = 20;
}
Could someone explain me why the binding between the view model and the user control does not work?
Looking on the code it seems that you have more then one PageSize properties defined in different classes. And most probabbly, it's difficult to understand just by looking on code provided, you bind in XAML one property, but change the value of another one, instead. Vary the name of one of PageSize properties to be sure where exactly databinding going to read/write.
I think this should help.
Working with a colleague of mine, we found a solution for what I intended to do :
In the Xaml of the MyDataPager usercontrol :
<Grid x:Name="LayoutRoot" Background="White" Loaded="MyDataPager_Loaded">
....
going with this definition of MyDataPager_Loaded :
void MyDataPager_Loaded(object sender, RoutedEventArgs e)
{
((Grid)sender).DataContext = this;
}
From the code above we have changed the ctor of the MyDataPager usercontrol to remove the datacontext binding :
public MyDataPager()
{
//DataContext = this;
Working this way, I am able to bind values in the main usercontrol like this :
<local:MyDataPager PageSizePager="{Binding Path=PageSize,Mode=TwoWay}" x:Name="MyDataPager1" />
So that the binding is made upon properties of the childusercontrol, not upon its control(ie: look of the child control may change without problems), and so that the child usercontrol does not have to use any "known" values from the datacontext of the main usercontrol.
Thanks for reading and for your support, it was greatly welcomed.I Hope these lines might serve another one in need of this.
Related
I've got a UserControl with a DependencyProperty created in its CodeBehind:
public partial class PanelMap_Control : UserControl
{
public ObservableCollection<GMapMarker> Markers
{
get { return (ObservableCollection<GMapMarker>)GetValue(MarkersProperty); }
set { SetValue(MarkersProperty, value); }
}
public static readonly DependencyProperty MarkersProperty = DependencyProperty.Register("Markers", typeof(ObservableCollection<GMapMarker>), typeof(PanelMap_Control), new PropertyMetadata(null));
}
Inside the UserControl is a Map, which contains the original collection of markers (not a DependencyProperty). I need to make this available for binding outside the UserControl, so at the end of the constructor (once the map's Markers are all setup), I set it to the control's DependencyProperty:
public PanelMap_Control()
{
InitializeComponent();
//...map setup...
this.Markers = _map.Markers;
}
Then, users of the UserControl can bind like:
<local:PanelMap_Control Markers="{Binding Path=MapMarkers, Mode=OneWayToSource}"/>
Where in the ViewModel:
public ObservableCollection<GMap.NET.WindowsPresentation.GMapMarker> MapMarkers
{
private get
{
return _mapMarkers;
}
set
{
_mapMarkers = value;
}
}
private ObservableCollection<GMap.NET.WindowsPresentation.GMapMarker> _mapMarkers;
The problem is, the ViewModel's MapMarkers property always ends up with the default value "null." I tried setting breakpoints on the SetValue call in PanelMap_Control and the ViewModel property's setter. The debugger first hits SetValue(), at which point _map.Markers is valid (non-null). THEN it hits the ViewModel's setter, with a value of null - and never reflects the actual valid object I pass to SetValue().
I'm sure I'm just missing something simple, but I can't for the life of me figure out why SetValue(non-null) would be followed by ViewModel.MapMarkers.set(null)...and never again.
(Side note 1: I realize this DependencyProperty won't work for TwoWay binding - i.e. I can't update _map.Markers in the UserControl. That's fine, I only need to read from it externally.)
(Side note 2: The _map.Markers object will never be changed - only the items in the collection - so setting the DependencyProperty to the initial collection should be sufficient.)
Please refer to the following questions.
OneWayToSource Binding seems broken in .NET 4.0
OneWayToSource binding resetting target value
OneWayToSource does actually reset the target property (Markers) initially. This is by design. There is more information about this on the links above.
As a workaround you could try to set up the binding programmatically:
public MainWindow()
{
InitializeComponent();
var vm = new ViewModel();
DataContext = vm;
vm.MapMarkers = control.Markers;
BindingOperations.SetBinding(control, PanelMap_Control.MarkersProperty, new Binding("MapMarkets") { TargetNullValue = control.Markers, FallbackValue = control.Markers });
}
XAML:
<local:PanelMap_Control x:Name="control" />
My grid:
<dxg:GridControl x:Name="StatisticsGridLevel1"
dx:ThemeManager.ThemeName="Office2013"
DataContext="{Binding FooViewModel}"
ItemsSource="{Binding FooCollection}">
ViewModel:
private List<FooDto> fooCollection = new List<FooDto>();
public List<FooDto> FooCollection
{
get
{
return this.fooCollection;
}
private set
{
this.fooCollection = value;
this.NotifyPropertyChanged();
}
}
And example method:
private void Foo()
{
foreach (var element in collection)
{
this.fooCollection.Add(new FooDto()
{
X = element.Foo1,
Y = element.Foo2,
Z = element.Foo3
});
}
this.NotifyPropertyChanged("FooCollection");
}
When I use ObservableCollection, everything works fine. But I want to use the List (that's not to notify in the loop).
The view refreshes after the start scroll on the grid. What is the problem?
I think a CollectionViewSource would work in your case. There are a lot of ways to go about creating one, in XAML, in your ViewModel, in your View's code-behind. I will throw together the easiest one for demonstration purposes which is creating a CollectionViewSource property on your ViewModel. I think some people might not necessarily like this approach - it kind of has the feel of mixing concerns. I am not sure I agree, though. If you take the position that a CollectionViewSource is an object model for a collection's view then I don't see anything wrong with having it in your ViewModel. But I think because it inherits from DependencyObject it gets stigmatized as being more of a view concern. Anyway, something like this would do what you want:
// Assuming this is your constructor
public ViewModel()
{
this.FooViewSource.Source = this.fooCollection;
}
private readonly List<FooDto> fooCollection = new List<FooDto>();
private readonly CollectionViewSource fooViewSource;
public CollectionViewSource FooViewSource
{
get { return this.fooViewSource; }
}
private void Foo()
{
foreach (var element in collection)
{
this.fooCollection.Add(new FooDto()
{
X = element.Foo1,
Y = element.Foo2,
Z = element.Foo3
});
}
this.FooViewSource.View.Refresh();
}
Then you would bind your ItemsSource property to the FooViewSource property of your ViewModel. A CollectionViewSource is pretty handy for other things as well. It supports sorting, filtering, selected items, maybe some other things I am forgetting.
I came from a Linux heavy environment, where I wrote most of my tools in Python but now I am in a windows heavy environment, and need to share my tools with my team and some need to be GUI driven so I am trying to learn C#/WPF. I'm getting confused on Data Binding to an ObservableCollection in the code behind. I can get it work, but I don't understand why, which bothers me.
My code is simple, and I am literally just trying to get the basics working so I can move on to more complicated parts:
XAML:
<ListView x:Name="lvUrlList" HorizontalAlignment="Left" Height="441" Margin="15,62,0,0" VerticalAlignment="Top" Width="486" SelectionChanged="listView_SelectionChanged" ItemsSource="{Binding Path=urlList, ElementName=MainWindow1}">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding domain}"/>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code Behind:
namespace ReferalScraper
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
//public ObservableCollection<Url> urlList { get; set; } // Method 1
public ObservableCollection<Url> urlList = new ObservableCollection<Url>(); // Method 2
public MainWindow()
{
// urlList = new ObservableCollection<Url>(); // Method 1
InitializeComponent();
urlList.Add(new Url() { domain = "www.test1.com" });
}
public class Url
{
public string domain { get; set; }
}
private void button_Click(object sender, RoutedEventArgs e)
{
urlList.Add(new Url() { domain = "www.test2.com" });
urlList.Add(new Url() { domain = "www.test3.com" });
}
}
}
The uncommented method for creating and instantiating the ObservableCollection doesn't work, the code compiles but I get the output error:
System.Windows.Data Error: 40 : BindingExpression path error: 'urlList' property not found on 'object' ''MainWindow' (Name='MainWindow1')'. BindingExpression:Path=urlList; DataItem='MainWindow' (Name='MainWindow1'); target element is 'ListView' (Name='lvUrlList'); target property is 'ItemsSource' (type 'IEnumerable')
Which I understand that it means it can't find the urlList object in MainWindow. But I don't understand why it can't find it.
If I switch to the Method 1 and uncomment the following two lines (and comment out the "Method 2" part) it works fine:
public ObservableCollection<Url> urlList { get; set; }
...
public MainWindow(){
urlList = new ObservableCollection<Url>()
Why is declaring the ObserverableCollection with the { get; set } needed? I don't quite grasp why I can't just instantiate my ObservableCollection as an empty ObserverableCollection like I am in Method 2.
I'm feeling incredibly dense, and haven't quite been able to track down the right terminology to even get close to answering my questions. Can anyone explain to a not so bright fellow what I am missing?
I have a feeling this is some C# understanding that I am missing.
The { get; set; } syntax defines your uriList as a property (an auto-implemented property, in this case ). Without this, uriList is simply a field.
WPF Data Binding cannot bind to fields. See this related question for some further discussion as to why this is the case.
Generally in C# fields are not usually exposed as public, properties are preferred. This allows you to change the get/set implementation if required. As an aside, the naming convention for properties is PascalCased (so UriList rather than uriList).
You use ElementName binding when you try to get the Property you are binding, from another FramworkElement's property, which isn't the case here,
you need to first: properly set the DataContext either from the codebehind in the MainWindow constructor:
this.DataContext=this;
or from the Xaml in the Window
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Then declare you UriList as a Property so it can be used with binding
private ObservableCollection<Url> _uriList = new ObservableCollection<Url()
{
new Url() { domain = "www.test2.com" }
};
public bool UrlList
{
get
{
return _uriList;
}
set
{
if (_uriList == value)
{
return;
}
_uriList = value;
}
}
and change your binding to the following :
ItemsSource="{Binding UrlList}
I am new to binding. I have binded slider value to my control's property and my controls property get changed when I change the slider value.
Now, when I need to change the slider value by changing my property value, it does not work..
I modified the xaml from some internet source, but still not get the expected output.
can anyone help me out...
<Grid>
<cc:MyControl Name="mycntrl" ZoomPercentage="{Binding ElementName=slider,Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></cc:MyControl>
<Slider Name="slider" Margin="20,20,20,400" Minimum="100" Maximum="400"></Slider>
</Grid>
Updated:
My code behind for my ZoomPercentage dependency property is below
public double ZoomPercentage
{
get
{
return (double)GetValue(ZoomPercentageProperty);
}
set
{
SetValue(ZoomPercentageProperty, value);
}
}
My dependency registration
public static readonly DependencyProperty ZoomPercentageProperty = DependencyProperty.Register("ZoomPercentage", typeof(double), typeof(MyControl), new FrameworkPropertyMetadata(ZoomPercentagePropertyChanged));
public static void ZoomPercentagePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (args.OldValue != null)
{
if ((double)args.NewValue != (double)args.OldValue)
{
MyControl mycontrol = obj as MyControl;
mycontrol .ZoomTo((int)((double)args.NewValue));
}
}
}
Your ZoomPercentage property should be implemented as a Dependencyproperty
Something like this
public class MyControl:UserControl
{
public MyControl() : base() { }
public double ZoomPercentage
{
get { return (double)this.GetValue(ZoomPercentageProperty); }
set { this.SetValue(ZoomPercentageProperty, value); }
}
public static readonly DependencyProperty ZoomPercentageProperty = DependencyProperty.Register(
"ZoomPercentage", typeof(double), typeof(MyControl:),new PropertyMetadata(0));
}
read more here
If you want a data bound control in the UI to update after changes made in code then you have to do one of two things. One option is to correctly implement the INotifyPropertyChanged interface in the class that you declared your Value property.
The other is to declare your Value property as a DependencyProperty, although you should only really do this in the code behind of your Window or UserControl and opt for the first method if you are using a view model. The purpose of these two methods is for you to 'plug in' to WPF notification framework, so that your UI control will update. Please read the linked pages for more information.
So here I come creating a user control. It consists of a treeview dropping down from a combobox.
Actually, there is a button with a control (DropTree) dropping down from its contextmenu. So I have a control DropTree.
public partial class DropTree : UserControl
{
public TreeView TreeView
{ get{return treeView;} }
public DropTree()
{ InitializeComponent(); }
}
to simplify it, I made the TreeView control public, then I have my main control which is called ComboTreeView.
Now I need to represent some treeview properties in it, so I define several dependency properties:
public static DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(ComboTreeView), new FrameworkPropertyMetadata { Inherits = true, IsNotDataBindable = false, DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
and in constructor it is:
public ComboTreeView()
{
InitializeComponent();
TreeViewControl.SetBinding(TreeView.SelectedItemProperty, new Binding("SelectedItem") { Source = this, Mode = BindingMode.TwoWay });
}
and it all seems ok, until i run it. It crashes saying that SelectedItem cannot be binded to data. I don't understand?
The same goes for ItemsSource and SelectedValue... but only SelectedValuePath property defined this way goes fine.
Can anybody help? Or is there any other way to bind it correctly?
PS: by the way, I need to use DataBinding for ComboTreeView in my code later.
Try to set the Binding on SelectedValue instead of SelectedItem.
TreeView.SelectedItem is a readonly property. You can't set it, whether explicitly or through binding. In order to select a node in a TreeView, you must set the TreeViewItem.IsSelected property to true.