I'm basically asking the same question as this person, but in the context of the newer x:Bind.
ViewModels' DataContext is defined like so
<Page.DataContext>
<vm:ChapterPageViewModel x:Name="ViewModel" />
</Page.DataContext>
So whenever I need to bind something I do it explicitely to the ViewModel like so
ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}"
However that doesn't work within templates
<FlipView ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}">
<FlipView.ItemTemplate>
<DataTemplate x:DataType="models:Image">
<ScrollViewer SizeChanged="{x:Bind ViewModel.PageResized}"> <-- this here is the culprit
<Image Source="{x:Bind url}"/>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
Reading the documentation, I found that using Path should basically reset the context to the page, but this (x:Bind Path=ViewModel.PageResizeEvent didn't work either. I'm still getting Object reference not set to an instance of an object, which should mean that it doesn't see the method (but a null).
Image class:
public class Image {
public int page { get; set; }
public string url { get; set; }
public int width { get; set; }
public int heigth { get; set; }
}
And in the ChapterPageViewModel
private List<Image> _pageList;
public List<Image> pageList {
get { return _pageList; }
set { Set(ref _pageList, value); }
}
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode,
IDictionary<string, object> suspensionState)
{
Initialize();
await Task.CompletedTask;
}
private async void Initialize()
{
pageList = await ComicChapterGet.GetAsync(_chapterId);
}
public void PageResized(object sender, SizeChangedEventArgs e)
{
//resizing logic happens here
}
We have two problems here:
First, trying to directly bind an event to a event handler delegate
That will never work, simply put.
One way to handle an event on MVVM pattern is by using EventTrigger and ICommand.
It requires a class that implements ICommand. This post will help you if don't know how to do it. I'll call mine DelegateCommand.
Here's how I would refactor it in two steps:
1) Add a Command to the VM:
public class ChapterPageViewModel
{
public ChapterPageViewModel()
{
this.PageResizedCommand = new DelegateCommand(OnPageResized);
}
public DelegateCommand PageResizedCommand { get; }
private void OnPageResized()
{ }
}
2) Bind that Command to the SizeChanged event with EventTrigger and InvokeCommandAction.
<Page (...)
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core">
(...)
<FlipView ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}" >
<FlipView.ItemTemplate>
<DataTemplate x:DataType="models:Image">
<ScrollViewer>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SizeChanged">
<core:InvokeCommandAction
Command="{x:Bind ViewModel.PageResizedCommand }" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
<Image Source="{x:Bind url}"/>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
</Page>
"But Gabriel", you say, "that didn't work!"
I know! And that's because of the second problem, which is trying to x:Bind a property that does not belong to the DataTemplate class
This one is closely related to this question, so I´ll borrow some info from there.
From MSDN, regarding DataTemplate and x:Bind
Inside a DataTemplate (whether used as an item template, a content
template, or a header template), the value of Path is not interpreted
in the context of the page, but in the context of the data object
being templated. So that its bindings can be validated (and efficient
code generated for them) at compile-time, a DataTemplate needs to
declare the type of its data object using x:DataType.
So, when you do <ScrollViewer SizeChanged="{x:Bind ViewModel.PageResized}">, you're actually searching for a property named ViewModel on the that models:Image class, which is the DataTemplate's x:DataType. And such a property does not exist on that class.
Here, I can see two options. Choose one of them:
Add that ViewModel as a property on the Image class, and fill it up on the VM.
public class Image {
(...)
public ChapterPageViewModel ViewModel { get; set; }
}
public class ChapterPageViewModel
{
(...)
private async void Initialize() {
pageList = await ComicChapterGet.GetAsync(_chapterId);
foreach(Image img in pageList)
img.ViewModel = this;
}
}
With only this, that previous code should work with no need to change anything else.
Drop that x:Bind and go back to good ol'Binding with ElementName.
<FlipView ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}" x:Name="flipView">
<FlipView.ItemTemplate>
<DataTemplate x:DataType="models:Image">
<ScrollViewer>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SizeChanged">
<core:InvokeCommandAction
Command="{Binding DataContext.PageResizedCommand
, ElementName=flipView}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
<Image Source="{x:Bind url}"/>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
This one kind of defeat the purpose of your question, but it does work and it's easier to pull off then the previous one.
Related
I have a custom UserControl called ReferencedItem. It should take a Guid called ItemId. It is implemented as such:
private static void OnItemIdChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs dpArgs)
{
//Do something
}
public static readonly DependencyProperty ItemIdProperty = DependencyProperty.Register("ItemId", typeof(Guid?), typeof(ReferencedItem), new FrameworkPropertyMetadata(
// use an empty Guid as default value
Guid.Empty,
// tell the binding system that this property affects how the control gets rendered
FrameworkPropertyMetadataOptions.AffectsRender,
// run this callback when the property changes
OnItemIdChanged
));
public Guid? ItemId
{
get { return (Guid?)GetValue(ItemIdProperty); }
set { SetValue(ItemIdProperty, value); }
}
public ReferencedItem()
{
InitializeComponent();
ViewModel = new ReferencedItemCtrlViewModel();
DataContext = ViewModel;
}
The ItemsSource will be made up of Reference objects defined as:
public class Reference
{
public Guid Id { get; set; }
}
Now when binding this ReferencedItem the value is not set as intended. Here is the code I want to work, but does not bind as intended:
<ItemsControl x:Name="ReferenceStack" ItemsSource="{Binding References}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ReferencedItem ItemId="{Binding Id}" Height="30" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have tried:
<local:ReferencedItem ItemId="128d48f0-f061-49fb-af49-b8e4ef891d03" Height="30" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
This works as expected, the OnItemIdChanged method is triggered.
<Label Content="{Binding Id}" HorizontalAlignment="Left" VerticalAlignment="Top" Height="30" Width="90"/>
This works as expected, a label is rendered with the Id.
Is there something I'm missing here? From what I can tell the data is available at bind time -- it just doesn't bind under the exact conditions I need it to :)
Thanks for any input!
EDIT:
Here is the code-behind for ReferencedItemList, the first block of XAML posted above:
public partial class ReferencedItemList : UserControl
{
protected ReferencedItemListCtrlViewModel ViewModel;
public ReferencedItemList()
{
InitializeComponent();
ViewModel = new ReferencedItemListCtrlViewModel();
DataContext = ViewModel;
}
public void Load(Guid id, string name)
{
ViewModel.Load(id, name);
//ReferenceStack.ItemsSource = ViewModel.References;
}
}
The commented line has been experimented with in place of the ItemsSource="{Binding References}" that was defined in the XAML.
I don't think I can successfully post the code for ReferencedItemListCtrlViewModel without going down a rabbit hole -- needless to say it has a property References of type ObservableCollection<Reference> where Reference is defined earlier in this post.
ReferencedItem.xaml:
<v:BaseUserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</v:BaseUserControl.Resources>
<StackPanel Orientation="Horizontal">
<Image x:Name="LinkIcon" Visibility="{Binding HasReference, Converter={StaticResource BooleanToVisibilityConverter}}" ToolTip="View Referenced Item" Source="/Images/link.png" Height="18" MouseUp="LinkIcon_MouseUp"/>
<TextBlock x:Name="ReferencedObjectDesc" Text="{Binding ReferenceHierarchy}" FontStyle="Italic" VerticalAlignment="Center" />
</StackPanel>
I just wanted to post the answer (explanation) I came across.
The problem was changing the DataContext of my ReferencedItem user control in the constructor. The view would instantiate a ReferencedItem and alter the DataContext - so once it was time to bind I had already flipped the context from the intended Reference.
There are multiple ways to resolve the timing - all project dependent. Either avoid setting the DataContext all together, set it post binding, or change the context on other items as appropriate.
Much thanks to Sinatr, Andrew Stephens, and Mike Strobel for all mentioning this at one point or another -- just took me some time to actually reach it. I don't think there's a way to assign credit to a comment, but let me know if there is.
I have a user control which have one TextBox and one Button called View. The TextBox takes index values.
On my Main view, I have one list view which will display all lines in a file.
An ObservableCollection is Bind to this.
What I need is, when index value is entered in the TextBox and View Button is clicked(in the user control), the SelecedIndex of the ListView(in the Main) should be changed to the index value.
How can I achieve this using MVVM?
Also, Please provide the proper approach to do this if I am doing it wrong.
Here is my UserControl Code:
XAML
<UserControl.DataContext>
<VM:IndexView_VM ></VM:IndexView_VM>
</UserControl.DataContext>
<Grid Background="White">
<TextBlock Margin="10,12,168,9" Text="Index : "/>
<TextBox Text="{Binding Index}" x:Name="TB_Index" Margin="53,11,90,8" />
<Button Command="{Binding View_CMD}" x:Name="BT_View" Content="View" Margin="136,11,10,8" />
</Grid>
ViewModel
public class IndexView_VM : ViewModelBase
{
public IndexView_VM()
{
View_CMD = new RelayCommand(_View_CMD);
}
int _Index;
public int Index
{
get { return _Index; }
set
{
_Index = value;
RaisePropertyChanged();
}
}
public RelayCommand View_CMD { get; set; }
internal void _View_CMD(object Parameter)
{
// What to write here?
}
}
Here is the Main View:
XAML
<UserControl.DataContext>
<VM:MainView_VM></VM:MainView_VM>
</UserControl.DataContext>
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="111*"/>
<ColumnDefinition Width="100*"/>
</Grid.ColumnDefinitions>
<StackPanel>
<local:IndexView/>
<local:IndexView/>
<local:IndexView/>
<local:IndexView/>
</StackPanel>
<ListView ItemsSource="{Binding FileData}" x:Name="listView" Grid.Column="1" >
<ListView.View>
<GridView>
<GridViewColumn Header="Data" Width="100"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
View Model
public class MainView_VM : ViewModelBase
{
public MainView_VM()
{
ReadFile();
}
public ObservableCollection<string> FileData { get; set; }
void ReadFile()
{
//I will read file here.
}
}
Here is a very basic example of what you want to do:
Window XAML
<Window.Resources>
<local:StringToIntConverter x:Key="StringToIntConverter" />
</Window.Resources>
<StackPanel>
<TextBlock>Select Index</TextBlock>
<TextBox x:Name="theTextBox" Text="{Binding SelectedIndex,ElementName=theList,Converter={StaticResource StringToIntConverter},Mode=OneWayToSource,UpdateSourceTrigger=Explicit}" />
<Button Click="Button_Click">Select Now</Button>
<ListBox x:Name="theList">
<ListBoxItem>First</ListBoxItem>
<ListBoxItem>Second</ListBoxItem>
<ListBoxItem>Third</ListBoxItem>
</ListBox>
</StackPanel>
Window CodeBehind
private void Button_Click(object sender, RoutedEventArgs e)
{
theTextBox.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
}
Explanation
You were asking to set the index on a list from a textbox by clicking a button. We bind the TextBox to the selected index with a couple of settings:
Source: SelectedIndex
ElementName: The List/Target UI Element
Converter: Required to get from String to Int (i wrote on myself)
Mode: OneWayToSource, we force the textbox to only send values to the list and not the other way round
UpdateSourceTrigger: We do not want the binding to auto-update, we want to do this ourselves
To update the binding we use the Click Event of the button.
But what about the view model?
The operation is a View-Only operation, the ViewModel doesn't need to know anything about it, so we should leave it out of the operation. That's why I'm not using a CommandBinding.
Whoops, forgot about the UserControl
If you want to put this in a user control then i suggest that you don't create a ViewModel at all. Also in the user control you don't need DataBinding, only on the outside. Keep it simple:
UserControl XAML
<TextBlock>Select Index</TextBlock>
<TextBox x:Name="theTextBox" />
<Button Click="Button_Click">Select Now</Button>
UserControl CodeBehind
public int Index
{
get { return (int)GetValue(IndexProperty); }
set { SetValue(IndexProperty, value); }
}
public static readonly DependencyProperty IndexProperty =
DependencyProperty.Register("Index", typeof(int), typeof(ListViewIndexSelectorControl), new PropertyMetadata(0));
private void Button_Click(object sender, RoutedEventArgs e)
{
if(int.TryParse(theTextBox.Text, out int result))
{
Index = result;
}
}
MainWindow Usage XAML
<local:ListViewIndexSelectorControl Index="{Binding SelectedIndex,ElementName=theList,Mode=OneWayToSource}" />
<ListBox x:Name="theList">
<ListBoxItem>First</ListBoxItem>
<ListBoxItem>Second</ListBoxItem>
<ListBoxItem>Third</ListBoxItem>
</ListBox>
If you later on need a ViewModel, you can also use the View as the ViewModel for simple controls, just set DataContext = this; in the View's constructor or use a Name on the XAML element and bind the DataContext by ElementName.
If you are using MvvmLight, you could for example use the Messenger class to send a message from the child view model to the parent:
IndexView_VM:
internal void _View_CMD(object Parameter)
{
GalaSoft.MvvmLight.Messaging.Messenger.Default.Send(_Index);
}
MainView_VM:
public class MainView_VM : ViewModelBase
{
public MainView_VM()
{
ReadFile();
GalaSoft.MvvmLight.Messaging.Messenger.Default.Register<int>(this, index => Index = index);
}
public ObservableCollection<string> FileData { get; set; }
int _Index;
public int Index
{
get { return _Index; }
set
{
_Index = value;
RaisePropertyChanged();
}
}
void ReadFile()
{
//I will read file here.
}
}
MainView:
<ListView ItemsSource="{Binding FileData}" x:Name="listView" Grid.Column="1"
SelectedIndex="{Binding Index}">
<ListView.View>
<GridView>
<GridViewColumn Header="Data" Width="100"/>
</GridView>
</ListView.View>
</ListView>
Please refer to Laurent Bugnion's MSDN Magazine article for more information about the Messenger.
MVVM - Messenger and View Services in MVVM: https://msdn.microsoft.com/en-us/magazine/jj694937.aspx
Not sure if I understand this right. You want to set the selected item of your ListBox using the index value you type in that TextBox, is this right?
If this is is right then I have the following considerations:
In this scenario a UserControl is probably overkill. Especially if you are going to never re-use it somewhere else in your UI.
Given the first consideration, all you have to do is move that TextBox to your main view, give it a name and bind the .Text Property to your ListView's SelectedIndex Property.
Example:
<ListView SelectedIndex="{Binding ElementName=txtIndex, Path=Text, Converter={StaticResource StringToIntConverter}, UpdateSourceTrigger=PropertyChanged}" />
That converter (which you would need to implement) is needed because converting a string to an int won't probably happen automatically. UpdateSourceTrigger=PropertyChanged will update the target Property as soon as you type, removing the need for a button.
At that point you should be done.
If you absolutely need that UserControl, you could add a DependencyProperty exposing the TextBox's value. Then you could bind to that Property as in the example above.
I have seen some answers regarding WP8 or others, however it seems that there is no triggers in WP8.1 (Or I am missing something?)
I have a datatemplate bound from the code (it is a hub datatemplate, and I have a mix of static and dynamic hubsections, therefore this datatemplate needs to be set from the code).
This datatemplate is defined in a separate xaml file, it includes a listbox (or listview) with another datatemplate defined for the items.
I need to bind a command on the item's tap or listbox selectionchanged (or something equivalent). However, the tap event defined in the template is not called, therefore I thought of binding a command on an UI element, but these seems not to support Commands neither interactivity triggers.
Any clue on how to handle that? :)
On the example below I don't get the event Item_Tapped nor ListBox_SelectionChanged, I would anyway prefer to bind one of these to a command in the viewmodel.
<DataTemplate x:Key="HubSectionTemplate">
<Grid>
<ListBox ItemsSource="{Binding MyNodes}"
SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="64" Tapped="Item_Tapped" >
<TextBlock Text="{Binding MyText}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</DataTemplate>
This is how it is used from code:
HubSection hs = new HubSection()
{
ContentTemplate = Application.Current.Resources[HUBSECTION_TEMPLATE] as DataTemplate,
DataContext = model,
Tag = model.UniqueId,
};
Hub.Sections.Insert(firstSectIdx + 1, hs);
public class Model
{
public Guid UniqueId {get;set;}
public List<ItemModel> MyNodes {get;set;}
}
public class ItemModel
{
public string MyText {get;set;}
}
PS: The ItemModel is defined in another assembly and therefore should not be edited (the command should be in the Model class if possible)
--- EDIT ---
In order to simplify the problem, I use the following models:
public class Model
{
public Guid UniqueId {get;set;}
public List<ItemModel> MyNodes {get;set;}
public ICommand MyCommand {get;set;}
}
public class ItemModel
{
Model _Model;
public ItemModel(Model m) {_Model = m; }
public string MyText {get;set;}
public ICommand MyCommand { get { return _Model.MyCommand; }}
}
And my (temporary) solution is to use a button in the itemtemplate:
<ListView.ItemTemplate>
<DataTemplate>
<Button HorizontalAlignment="Stretch" Command="{Binding TapCommand}" Height="64">
<TextBlock Text="{Binding MyText}" />
</Button>
</DataTemplate>
</ListView.ItemTemplate>
You can use Behaviors SDK.
In Visual Studio go to 'Tools -> Extension and updates' and install Behaviors SDK (XAML). Then reference it in your project using Add reference dialog.
After that add following namespaces to your page:
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
Now you can register events like tap on your stack panel using following syntax:
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="64">
<TextBlock Text="{Binding MyText}" />
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Tapped">
<core:InvokeCommandAction Command="{Binding YourCommand}"/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</StackPanel>
</DataTemplate>
However this code only works if your Command is defined in your ItemModel class. If you want to bind to the parent element Command, you can try something like this (not tested):
{Binding ElementName=LayoutRoot, Path=DataContext.ParentCommand}
But I would preferer having command on your ItemModel class
Edit: Solution without Behaviors SDK:
If you are using ListView (or something inherited from ListViewBase) you can use ItemClick event. To make it more reusable and Mvvm friendly you can implement your DependencyProperty like this:
public static class ItemClickCommand
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand),
typeof(ItemClickCommand), new PropertyMetadata(null, OnCommandPropertyChanged));
public static void SetCommand(DependencyObject d, ICommand value)
{
d.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject d)
{
return (ICommand)d.GetValue(CommandProperty);
}
private static void OnCommandPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var control = d as ListViewBase;
if (control != null)
{
control.ItemClick += OnItemClick;
}
}
private static void OnItemClick(object sender, ItemClickEventArgs e)
{
var control = sender as ListViewBase;
var command = GetCommand(control);
if (command != null && command.CanExecute(e.ClickedItem))
{
command.Execute(e.ClickedItem);
}
}
}
Then your ListView will look like this:
<ListView
IsItemClickEnabled="True"
helpers:ItemClickCommand.Command="{Binding YourCommand}"
ItemsSource="{Binding MyNodes}"
ItemTemplate="{StaticResource YourDataTemplate}" />
In this case your child item is passed to your command as a parameter, so it should also solve your problem with your Command defined in parent model.
I have a longlistselector and in each row I have ToggleSwitch and I would like to call http request via my ApiService when ToggleSwitch is changed. I have ApiService class in ViewModel thanks to injection and in ViewModel I have ObservableCollection of Modules which have switches. I bind it with datatemplate and there is no problem with bind ToggleSwitch to bool property. But what should I do in setter of that property?
Model - Modul.cs
public int IsLock
{
get { return isLock; }
set {
Set(() => IsLock, ref isLock, value);
// What should I do here? How call ViewModel method?
}
}
ViewModel - ModuleListViewModel.cs
public ObservableCollection<Module> Modules { get; private set; }
// here I have apiService instance
// and here I could call apiService.Lock(module) and so on
View - part of DataTemplate
<toolkit:ToggleSwitch x:Name="LockSwitch"
IsChecked="{Binding IsLock, Mode=TwoWay}"/>
What's the right aproach for this? Maybe I could have ApiService class in each Modul class but I think that's very bad. I think ViewModel should somehow findout that Model was changed and it should call method.
I suggest using the ToggleSwitch's Command property -- that will get executed every time the user changes the toggle, and will allow you to bind to the parent data context. Use something like this in the XAML:
<ItemsControl x:Name="items" ItemsSource="{Binding Modules}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<toolkit:ToggleSwitch x:Name="LockSwitch"
Command="{Binding ElementName=items,Path=DataContext.LockToggleCommand}"
CommandParameter="{Binding}"
IsChecked="{Binding IsLock, Mode=TwoWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Then just add the "LockToggleCommand" to your main view model, and call the service, eg:
public ObservableCollection<Module> Modules { get; private set; }
public ICommand LockToggleCommand { get; private set; }
public ViewModel()
{
LockToggleCommand = new DelegateCommand<Module>(module => {
apiService.Lock(module);
});
}
Here "DelegateCommand" is just the usual implementation of ICommand -- I am sure that MVVM Light has its own standard implementation.
Edit
I thought that ToggleSwitch supported Command, but since it doesn't, you can take a similar approach using an EventTrigger (if you are willing to add the System.Windows.Interactivity and Microsoft.Expression.Interactions DLLs to your project):
<ItemsControl x:Name="items" ItemsSource="{Binding Modules}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<toolkit:ToggleSwitch x:Name="LockSwitch"
IsChecked="{Binding IsLock, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Toggled">
<ei:CallMethodAction TargetObject="{Binding ElementName=items,Path=DataContext}"
MethodName="OnToggled"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now add the "OnToggled" method to the main view model -- use the "sender" parameter to get the current item, something like this:
public void OnToggled(object sender, RoutedEventArgs e)
{
var toggleSwitch = (ToggleSwitch)sender;
var module = (Module)toggleSwitch.DataContext;
apiService.Lock(module);
}
I submitted this accidently, I fixed it myself and wasn't going to submit after writing out the question. But have learnt from the comments, thanks!
I am trying to create a simple todo app in win8 and eventually want to hock it into ToDoIst API.
I have created a simple task class to try and get my head around the databinding however I just can not get it to do what I want to do. I have used listboxes and other basic form elements.
task.cs
class task
{
private string content;
private bool complete;
public string Content
{
get {return content;}
set { content = value; }
}
public bool Complete
{
get { return complete; }
set { complete = value; }
}
public task(string content)
{
Content = content;
Complete = false;
}
}
MainPage.xaml
And at the moment my XAML looks like this.
<GridView HorizontalAlignment="Left" Margin="482,190,0,0" VerticalAlignment="Top" Width="400" Height="500">
<ListView x:Name="LVtasks" HorizontalAlignment="Left" Height="500" VerticalAlignment="Top" Width="400" ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Content}"/>
<RadioButton/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</GridView>
I have put in some dummy data, 4 elements and when I run it, it comes up with 4 boxes with radio buttons however no text (there is space for the text) I am not sure how I would bind the bool?
I can not see what I am doing wrong. If anyone could help and point me in the right direction, I have searched a fair amount of tutorials and just can not figure it out.
Your code looks a little strange, maybe this is what you want:
<ListView x:Name="LVtasks" HorizontalAlignment="Left" Height="500" VerticalAlignment="Top" Width="400" ItemsSource="{Binding ToDoItems}">
<ListView.ItemTemplate>
<DataTemplate>
<RadioButton GroupName="ToDos" Content="{Binding Content}" IsChecked="{Binding IsComplete}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Do you really want radiobuttons? I think you want Checkboxes, the difference is that when you use radiobuttons only one in a group can be 'checked'
I used this code behind to have a datacontext:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ToDoItems = new ObservableCollection<TodoItem>(new List<TodoItem>
{
new TodoItem("Content1"),
new TodoItem("Content2")
});
this.DataContext = this;
}
public ObservableCollection<TodoItem> ToDoItems { get; set; }
}
I changed the name of task to ToDoItem Task is already a class in the framework and might cause confusion.
For the RadioButton IsChecked is the property to bind to a bool property:
<RadioButton IsChecked="{Binding Path=Complete}"/>
Your text is most likely not showing up because you haven't set up any change notifications and the binding is happening before you set the Content values. Using the INotifyPropertyChanged interface is the most common and usually the easiest way to do this.
Like John already mentioned you should really let the window containing your listview implement the INotifyPropertyChanged interface. And to set the data context of your window like Johan said. It is important that you call the propertychanged method in each setter of a property. It is also useful to use an ObservableCollection as ItemSource of your listview. Try to create an instance of ObservableCollection, create a property for it calling propertychanged method in its setter an set rhe ItemSource of your listview to the property. Do not forget to also call propertychanged whenever you add or remove items from the collection