I have a simple Behavior like that
public class KeyBoardChangeBehavior : Behavior<UserControl>
{
public Dictionary<string, int> DataToCheckAgainst;
protected override void OnAttached()
{
AssociatedObject.KeyDown += _KeyBoardBehaviorKeyDown;
}
protected override void OnDetaching()
{
AssociatedObject.KeyDown -= _KeyBoardBehaviorKeyDown;
}
void _KeyBoardBehaviorKeyDown(object sender, KeyEventArgs e)
{
// My business will go there
}
}
I want to asign value to this dictionary from the view , I call it as following
<UserControl x:Class="newhope2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:Behaviors="clr-namespace:newhope2"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Interactivity:Interaction.Behaviors>
<Behaviors:KeyBoardChangeBehavior />
</Interactivity:Interaction.Behaviors>
<Grid x:Name="LayoutRoot" Background="White">
</Grid>
</UserControl>
but how can I pass this dictionary to the behavior from XAML or its code behind
To take a Binding, a property needs to be a DependencyProperty.
You need to define the property, in the Behaviour, like so:
public Dictionary<string, int> DataToCheckAgainst
{
get { return (Dictionary<string, int>)GetValue(DataToCheckAgainstProperty); }
set { SetValue(DataToCheckAgainstProperty, value); }
}
public static readonly DependencyProperty DataToCheckAgainstProperty =
DependencyProperty.Register(
"DataToCheckAgainst",
typeof(Dictionary<string, int>),
typeof(KeyBoardChangeBehavior),
new PropertyMetadata(null));
Use the Visual Studio "propdp" snippet.
Usage is as Adi said, like so:
<Interactivity:Interaction.Behaviors>
<Behaviors:KeyBoardChangeBehavior DataToCheckAgainst="{Binding MyDictionary}" />
</Interactivity:Interaction.Behaviors>
All you need to do is declare the dictionary as a property and then pass it a value via binding.
In the behavior:
public Dictionary<string, int> DataToCheckAgainst { get; set; }
In XAML:
<Interactivity:Interaction.Behaviors>
<Behaviors:KeyBoardChangeBehavior DataToCheckAgainst="{Binding MyDictionary}" />
</Interactivity:Interaction.Behaviors>
Related
I've got a class that has an ObservableCollection of itself embedded within the class.
I'm trying to create a user control that also has a reference to itself in order to display the contents of the observable collection. However, I'm getting a runtime error whenever I'm trying to run the app.
The error is not overly meaningful:
XAML parsing failed.
E_RUNTIME_SETVALUE [Line: 91 Position: 58] (which is the line that has the recursive call to the user control)
The class looks something like this (it's been made shorter for illustration purposes)
public class BookChapterVm : IBookChapterVm
{
public int Id {get;set;}
public string ChapterText {get;set;}
public ObservableCollection<IBookChapterVm> Chapters { get; set; } = new ObservableCollection<IBookChapterVm>();
}
The user control looks something like this (again, unnecessary parts are removed)
<UserControl
x:Class="Cgs.Ux.UserControls.HelpTextEditor.BookChapterEditorCtrl">
<ListView
ItemsSource="{x:Bind Vm.Chapters, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="help:BookChapterVm">
<StackPanel Orientation="Horizontal">
<local:BookChapterEditorCtrl Vm="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>
I've also tried to set up a recursive data template, but it basically ended up with the same error.
Here is a working exemple :
In your page :
<local:RecursiveContainer ViewModel="{Binding}" />
The code behind :
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = BuildBookChapterVM();
}
private BookChapterVM BuildBookChapterVM()
{
BookChapterVM vm1 = new BookChapterVM { ChapterText = "1" };
BookChapterVM vm21 = new BookChapterVM { ChapterText = "21" };
BookChapterVM vm22 = new BookChapterVM { ChapterText = "22" };
BookChapterVM vm211 = new BookChapterVM { ChapterText = "211" };
vm1.Chapters.Add(vm21);
vm1.Chapters.Add(vm22);
vm21.Chapters.Add(vm211);
return vm1;
}
}
public class BookChapterVM
{
public int Id { get; set; }
public string ChapterText { get; set; }
public ObservableCollection<BookChapterVM> Chapters { get; set; } = new ObservableCollection<BookChapterVM>();
}
The UserControl XAML :
<UserControl
x:Class="WpfApp2.RecursiveContainer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp2"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<StackPanel>
<TextBlock Text="{Binding ChapterText}" />
<ItemsControl HorizontalContentAlignment="Stretch" ItemsSource="{Binding Chapters, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:RecursiveContainer
Margin="10,5,0,5"
HorizontalAlignment="Stretch"
ViewModel="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</UserControl>
The UC code behind :
public partial class RecursiveContainer : UserControl
{
public RecursiveContainer()
{
InitializeComponent();
}
public BookChapterVM ViewModel
{
get { return (BookChapterVM)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(RecursiveContainer), typeof(RecursiveContainer));
}
See image as proof of concept.
I hope it will help you ;)
Here is my usercontrol XAML
<UserControl x:Class="UserControlTest.Controls.FullNameControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:UserControlTest.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="100"
d:DesignWidth="100"
mc:Ignorable="d">
<Grid>
<TextBlock Text="{Binding FirstNameText}" />
</Grid>
</UserControl>
Here is the code behind for the usercontrol
public sealed partial class FullNameControl : UserControl
{
public FullNameControl()
{
this.InitializeComponent();
}
public string FirstNameText
{
get { return (string)GetValue(FirstNameTextProperty); }
set { SetValue(FirstNameTextProperty, value); }
}
public static readonly DependencyProperty FirstNameTextProperty =
DependencyProperty.Register("FirstNameText", typeof(string),
typeof(FullNameControl), new PropertyMetadata(String.Empty));
}
Here is the page that uses the usercontrol
<Page x:Class="UserControlTest.Views.PageWithUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Behaviors="using:Template10.Behaviors"
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:controls="using:Template10.Controls"
xmlns:uControls="using:UserControlTest.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:UserControlTest.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:UserControlTest.ViewModels"
mc:Ignorable="d">
<Page.DataContext>
<vm:PageWithUserControlViewModel x:Name="ViewModel" />
</Page.DataContext>
<Grid>
<uControls:FullNameControl FirstNameText="{Binding FirstName, Mode=OneWay}" />
</Grid>
</Page>
Here is the viewmodel that populates the view.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Template10.Common;
using Template10.Mvvm;
using Template10.Services.NavigationService;
using Windows.UI.Xaml.Navigation;
namespace UserControlTest.ViewModels
{
public class PageWithUserControlViewModel : ViewModelBase
{
public PageWithUserControlViewModel()
{
}
private string _FirstName = "Default";
public string FirstName { get { return _FirstName; } set { Set(ref _FirstName, value); } }
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> suspensionState)
{
FirstName = "Terrence";
await Task.CompletedTask;
}
public override async Task OnNavigatedFromAsync(IDictionary<string, object> suspensionState, bool suspending)
{
await Task.CompletedTask;
}
public override async Task OnNavigatingFromAsync(NavigatingEventArgs args)
{
args.Cancel = false;
await Task.CompletedTask;
}
}
}
So the missing part was to add this datacontext assignment code to the constructor of the user control after InitializeComponent call
public FullNameControl()
{
this.InitializeComponent();
(this.Content as FrameworkElement).DataContext = this;
}
I have a collection of Objects with dependencyproperties, and Observer patern as I feed realtime data in "AssetIHM" Object.
public class assetVM: DependencyObject ,IObserver
{
public assetVM(AssetIHM aihm)
{
ObserverCollectionSingleton.ObserverCollectionInstance.Add(this);
_aihm = aihm;
}
private string _assetname;
public string assetname
{
get { return _assetname; }
set
{
_assetname = value;
SetValue(assetnameprop, value);
}
}
public static readonly DependencyProperty assetnameprop =
DependencyProperty.Register("assetnameprop", typeof(string),typeof(assetVM), new UIPropertyMetadata(""));
...
I also have a UserControl, Which should display the information contained in the AssetVM object:
public partial class AssetPanel : UserControl
{
public assetVM Asset
{
get
{
return (assetVM)GetValue(assetProperty);
}
set
{
SetValue(assetProperty, value);
}
}
public static DependencyProperty assetProperty = DependencyProperty.Register(
"Asset", typeof(assetVM), typeof(AssetPanel), new PropertyMetadata(null, new PropertyChangedCallback(OnCurrentItemChanged)));
public AssetPanel(assetVM _Asset)
{
Asset = _Asset;
this.DataContext = this;
InitializeComponent();
...
}
public AssetPanel( )
{
this.DataContext = this;
InitializeComponent();
...
}
...
I my main windows, I have a ListBox,
<Window x:Class="ETRportfolio.MainWindow"
DataContext = "{Binding RelativeSource={RelativeSource Self}}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:UserControls="clr-namespace:ETRportfolio"
Title="MainWindow" Height="1200" Width="1425">
<StackPanel HorizontalAlignment="Left" Height="1153" VerticalAlignment="Top" Width="1400" Margin="10,10,-18,0">
<ListBox x:Name="Gridview" Height="800" >
<ListBox.ItemTemplate>
<DataTemplate>
<UserControls:AssetPanel Asset="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
MY problem is that I would like to feed my Usercontrol with the data contained is the collection of AssetVM.
public partial class MainWindow : Window
{
public static Dispatcher curDispatcher;
public ObservableCollection<assetVM> Datas
{
get
{
return (ObservableCollection<assetVM>)curDispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.DataBind,
(DispatcherOperationCallback)delegate { return GetValue(DataSProperty); },
DataSProperty);
}
set
{
curDispatcher.BeginInvoke(DispatcherPriority.DataBind,
(SendOrPostCallback)delegate { SetValue(DataSProperty, value); },
value);
}
}
public readonly DependencyProperty DataSProperty = DependencyProperty.Register("DataS", typeof(ObservableCollection<assetVM>), typeof(MainWindow), new PropertyMetadata(null, new PropertyChangedCallback(OnCurrentItemChanged)));
public MainWindow()
{
this.DataContext = this;
curDispatcher = this.Dispatcher;
Datas =new ObservableCollection<assetVM>();
InitializeComponent();
Gridview.ItemsSource = Datas;
addasset.addbtn.Click += onclik;
}
When the AssetPanel constructor is created, it doesn't bind with My AssetVM datas. I always pass through the empty constructor.
How Can I do that in XAML?
I guess the problem is there:
ListBox.ItemTemplate>
<DataTemplate>
<UserControls:AssetPanel Asset="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
Thks!
Edit::
I removed this.DataContext = this;
In the UserControl constructor, but the UserControl Constructor called when a DataObject
assetVM
is added to the ObservableCollection Datas, used as datasource for the Listview, is this the empty constructor. but not:
public AssetPanel(assetVM _Asset)
> {
> Asset = _Asset;
> InitializeComponent();
> ValuationInfo.ItemsSource = new List<string> { "%", "Value" };
> AVbox.ItemsSource = Enum.GetValues(typeof(AV)).Cast<AV>();
> }
So it doesn't bind.
Edit2::
private static void OnCurrentItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
AssetPanel instance = (AssetPanel)d;
instance.Asset = (assetVM)e.NewValue;
return;
}
It binds ! :)
Nevertheless, the Registered Dependency properties in the dataobject are not displayed.
This is my Assetpanel xaml:
<UserControl x:Class="ETRportfolio.AssetPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="152" d:DesignWidth="1400">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border BorderBrush="#FFDE6A6A" BorderThickness="1" Grid.Row="0" Grid.Column="0" Background="lightblue">
<TextBlock x:Name="assetnamebox" TextWrapping="Wrap" Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl }, Path = assetnameprop, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="15"/>
</Border>
...
TextBlock x:Name="assetnamebox" TextWrapping="Wrap" Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl }, Path = assetnameprop,
is not binding with assetVM.assetname VIA the DependencyProperty assetnameprop.
What is wrong there?
thks
Setting a UserControl's DataContext to itself, as done in your constructors by the statements
this.DataContext = this;
effectivly disables binding to properties of inherited DataContexts, like in
<ListBox.ItemTemplate>
<DataTemplate>
<UserControls:AssetPanel Asset="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
where the binding source is the inherited DataContext of the ListBoxItems, i.e. an assetVM instance.
Remove the DataContext assignment from your UserContr's constructors.
I believe this is possible, but I don't seem to be able to get it to work; here's my view model:
namespace MyApp.ViewModel
{
public class MainViewModel : INotifyPropertyChanged
{
private static MainViewModel _mvm;
public static MainViewModel MVM()
{
if (_mvm == null)
_mvm = new MainViewModel();
return _mvm;
}
private string _imagePath = #"c:\location\image.png";
public string ImagePath
{
get { return _imagePath; }
set
{
SetProperty<string>(ref _imagePath, value);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (Equals(storage, value)) return false;
storage = value;
OnPropertyChanged<T>(propertyName);
return true;
}
private void OnPropertyChanged<T>([CallerMemberName]string caller = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(caller));
}
}
...
Here's my App.xaml:
xmlns:vm="using:MyApp.ViewModel">
<Application.Resources>
<ResourceDictionary>
<vm:MainViewModel x:Key="MainViewModel" />
</ResourceDictionary>
</Application.Resources>
Here's the binding:
<Page
...
DataContext="{Binding MVM, Source={StaticResource MainViewModel}}">
<StackPanel Orientation="Horizontal" Margin="20" Grid.Row="0">
<TextBlock FontSize="30" Margin="10">Image</TextBlock>
<TextBox Text="{Binding ImagePath}" Margin="10"/>
</StackPanel>
...
I don't seem to be able to get the binding to work; what have I missed here? I would expect the field to be populated with the default value, but it isn't; I've put breakpoints in the ViewModel, but it is not breaking.
To me your binding syntax is incorrect. DataContext="{Binding MVM, Source={StaticResource MainViewModel} means you should have a "MVM" PROPERTY in your MainViewModel class. In your case MVM is a method.
Try replacing your MVM method by a property. That might work.
Another way to do it, is to set
DataContext="{StaticResource MainViewModel}"
In that case, the MVM method will be obsolete (I did not try it on WinRT)
I'm trying to populate a list view control on a XAML page in a Win8 application. I've added the following attributes to the page XAML:
<common:LayoutAwarePage x:Class="SecAviTools.ViewWeatherHome"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:common="using:MyNameSpace.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:MyNameSpace"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodel="using:MyNameSpace.Win8ViewModel"
x:Name="pageRoot"
DataContext="{Binding DefaultViewModel,
RelativeSource={RelativeSource Self}}"
mc:Ignorable="d">
<!-- ... -->
<ListView ItemsSource="{Binding Path=viewmodel:Stations}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Id}"/>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
My source class is:
namespace MyNameSpace.Win8ViewModel
{
public class Stations : INotifyPropertyChanged, INotifyCollectionChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected void OnCollectionChanged<T>(NotifyCollectionChangedAction action, ObservableCollection<T> items)
{
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, items));
}
public Stations()
{
AllStations = new ObservableCollection<Station>();
AddStations(new List<Station>());
}
public ObservableCollection<Station> AllStations { get; private set; }
public void AddStations(List<Station> stations)
{
AllStations.Clear();
foreach (var station in stations)
AllStations.Add(station);
OnCollectionChanged(NotifyCollectionChangedAction.Reset, AllStations);
OnPropertyChanged("AllStations");
}
}
public class Station
{
public int Id { get; set; }
public string Name { get; set; }
}
}
There is also a button on the page (not shown here) that does the following:
public sealed partial class MyPage : MyNameSpace.Common.LayoutAwarePage
{
private Stations m_Stations = new Stations();
//...
private async void SearchButtonClick(object sender, RoutedEventArgs e)
{
var list = new List<Station>();
list.Add(new Station() { Id = 0, Name = "Zero" });
list.Add(new Station() { Id = 1, Name = "One" });
m_Stations.AddStations(list);
}
}
However, when I run the code, nothing appears in the list view. What am I missing?
TIA
You don't show what DefaultViewModel is, but I'll assume it's set to be an instance of the class you show, Stations. In that case, you need to binding to be:
<ListView ItemsSource="{Binding Path=AllStations}">
The Path of a binding usually refers to a property somewhere; with no further specification, such as a Source, it's a property on the object that is set to be the DataContext.
Regardless, you don't need the namespace qualifier "viewmodel:".
As an aside, if you do end up binding to the ObservableCollection, you don't need to implement INotifyCollectionChanged, only INotifyPropertyChanged for when the AllStations property is set.