How to link commands between user control and main in MVVM - c#

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.

Related

x:Bind ViewModel method to an Event inside DataTemplate

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.

MVVM Basic ContentControl Binding

I'm trying to figure out the correct way to bind a ViewModel to a ContentControl (I've looked all over the net but can't find an example that I can get to work correctly).
My Model:
public class Model
{
private string _Variable = "TEST";
public string Variable
{
get { return _Variable; }
set { _Variable = value; }
}
}
My ViewModel
public class ViewModel :ViewModelBase
{
private Model _Model = new Model();
public string Variable
{
get { return _Model.Variable; }
set
{
if (_Model.Variable != value)
{
_Model.Variable = value;
RaisePropertyChanged("Variable");
}
}
}
My View/Window
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type System:String}">
<TextBox/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentControl Content="{Binding Path=Variable}" />
</StackPanel>
So in essence, I have (or at least I believe I have) set the content of the ContentControl to the ViewModel property 'Variable', it is of type string so the only DataTemplate should be implemented and a Textbox displayed.
And that happens... A Textbox is displayed! However the Textbox is empty, and any changes made do not impact Variable.
This means I have made an error in the Batabinding, but I don't understand where. I have a feeling that just because my DataTemplate is displaying a Textbox, nothing is actually binding the string to it, but that is the bit I'm lost over.
Thanks for any help/advice.
You haven't specified the TextBox's Text binding, which is completely separate to the DataContext. Since you want the TextBox to bind to its own DataContext just do this:
<TextBox Text="{Binding Path=.}"/>
use textbox as below:
<TextBox Text="{Binding}" />

How to bind a command to StackPanel or Grid tap event?

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.

WPF Button Command binding

I have a MainWindow:Window class which holds all the data in my program. The MainWindow's .xaml contains only an empty TabControl which is dynamically filled (in the code-behind).
One of the tabs (OptionsTab) has its .DataContext defined as the MainWindow, granting it access to all of the data. The OptionsTab has a DataGrid which has a column populated with Buttons, as shown below:
The DataGrid is populated with DataGridTemplateColumns, where the DataTemplate is defined in the main <Grid.Resources>. I would like to bind this button to a function in the MainWindow (not the OptionsTab in which it resides).
When the OptionsTab is created, it's .DataContext is set as the MainWindow, so I would have expected that defining the DataTemplate as below would have done it.
<DataTemplate x:Key="DeadLoadIDColumn">
<Button Content="{Binding Phases, Path=DeadLoadID}" Click="{Binding OpenDeadLoadSelector}"/>
</DataTemplate>
I thought this would mean the Click event would be bound to the desired OptionsTab.DataContext = MainWindow's function.This, however, didn't work (the Content did, however). So then I started looking things up and saw this answer to another SO question (by Rachel, who's blog has been of great help for me), from which I understood that you can't {bind} the click event to a method, but must instead bind the Command property to an ICommand property (using the helper RelayCommand class) which throws you into the desired method. So I implemented that, but it didn't work. If I place a debug breakpoint at the DeadClick getter or on OpenDeadLoadSelector() and run the program, clicking on the button doesn't trigger anything, meaning the {Binding} didn't work.
I would like to know if this was a misunderstanding on my part or if I simply did something wrong in my implementation of the code, which follows (unrelated code removed):
MainWindow.xaml
<Window x:Class="WPF.MainWindow"
x:Name="Main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WPF" SizeToContent="WidthAndHeight">
<TabControl Name="tabControl"
SelectedIndex="1"
ItemsSource="{Binding Tabs, ElementName=Main}">
</TabControl>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
ICommand deadClick;
public ICommand DeadClick
{
get
{
if (null == deadClick)
deadClick = new RelayCommand(p => OpenDeadLoadSelector());
return deadClick;
}
}
public ObservableCollection<TabItem> Tabs = new ObservableCollection<TabItem>();
public static DependencyProperty TabsProperty = DependencyProperty.Register("Tabs", typeof(ICollectionView), typeof(MainWindow));
public ICollectionView ITabsCollection
{
get { return (ICollectionView)GetValue(TabsProperty); }
set { SetValue(TabsProperty, value); }
}
public ObservableCollection<NPhase> Phases = new ObservableCollection<NPhase>();
public static DependencyProperty PhasesProperty = DependencyProperty.Register("Phases", typeof(ICollectionView), typeof(MainWindow));
public ICollectionView IPhasesCollection
{
get { return (ICollectionView)GetValue(PhasesProperty); }
set { SetValue(PhasesProperty, value); }
}
public ObservableCollection<string> Loads = new ObservableCollection<string>();
public static DependencyProperty LoadsProperty = DependencyProperty.Register("Loads", typeof(ICollectionView), typeof(MainWindow));
public ICollectionView ILoadsCollection
{
get { return (ICollectionView)GetValue(LoadsProperty); }
set { SetValue(LoadsProperty, value); }
}
void OpenDeadLoadSelector()
{
int a = 1;
}
public MainWindow()
{
var optionsTab = new TabItem();
optionsTab.Content = new NOptionsTab(this);
optionsTab.Header = (new TextBlock().Text = "Options");
Tabs.Add(optionsTab);
ITabsCollection = CollectionViewSource.GetDefaultView(Tabs);
Loads.Add("AS");
Loads.Add("2");
InitializeComponent();
}
}
OptionsTab.xaml
<UserControl x:Class="WPF.NOptionsTab"
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"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
mc:Ignorable="d"
xmlns:l="clr-namespace:WPF">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="DeadLoadIDColumn">
<Button Content="{Binding Phases, Path=DeadLoadID}" Command="{Binding Path=DeadClick}"/>
</DataTemplate>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<!-- ... -->
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<!-- ... -->
</Grid>
<Grid Grid.Row="1">
<!-- ... -->
</Grid>
<l:NDataGrid Grid.Row="2"
x:Name="PhaseGrid"
AutoGenerateColumns="False">
<l:NDataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name}"/>
<DataGridTextColumn Header="Date (days)" Binding="{Binding Path=Date}"/>
<DataGridTemplateColumn Header="Deadload" CellTemplate="{StaticResource DeadLoadIDColumn}"/>
</l:NDataGrid.Columns>
</l:NDataGrid>
</Grid>
</UserControl>
OptionsTab.xaml.cs
public NOptionsTab(MainWindow w)
{
DataContext = w;
InitializeComponent();
PhaseGrid.ItemsSource = w.Phases;
}
While we're at it (and this might be a related question), why does {Binding Phases, Path=DeadLoadID} work on the DataTemplate (which is why the buttons appear with "Select"), but if I do {Binding Phases, Path=Name} in the PhaseGrid and remove the .ItemsSource code from the constructor, nothing happens? Shouldn't the PhaseGrid inherit its parent's (NOptionsTab / Grid) DataContext? Hell, even setting PhaseGrid.DataContext = w; doesn't do anything without the .ItemsSource code.
EDIT (27/04/14):
I think that knowing the contents of the NPhase class itself will be of use, so here it is:
public class NPhase : INotifyPropertyChanged
{
string name;
double date;
string deadLoadID = "Select";
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get { return name; }
set
{
name = value;
EmitPropertyChanged("Name");
}
}
public double Date
{
get { return date; }
set
{
date = value;
EmitPropertyChanged("Date");
}
}
public string DeadLoadID
{
get { return deadLoadID; }
set
{
deadLoadID = value;
EmitPropertyChanged("DeadLoadID");
}
}
void EmitPropertyChanged(string property)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public NPhase(double _date, string _name)
{
date = _date;
name = _name;
}
}
EDIT (29/04/14):
A simplified project (getting rid of everything that wasn't necessary) can be downloaded from here (https://dl.dropboxusercontent.com/u/3087637/WPF.zip)
I think that there is the problem that you do not specify data source properly for the data item inside your grid.
I think that the data source for your button column is NPhase instance. So it has no DeadClick property. So, you can check it using Output window in Visual Studio.
I suggest that you can do something like that:
<DataTemplate x:Key="DeadLoadIDColumn">
<Button Content="{Binding Phases, Path=DeadLoadID}"
Command="{Binding Path=DataContext.DeadClick, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type l:NDataGrid}}}"/>
</DataTemplate>
I currently do not understand how you can compile Content="{Binding Phases, Path=DeadLoadID}", because as I thought the default value for Binding clause is the Path property, and you have specified it twice.
EDIT
After I got the small solution all becomes clear. Here is the modified solution. All what I changed in it - I have added RelativeSource to the command binding as I described above, and I added MainWindow as DataContext for your OptionsTab (you have specified it in the question, but not in the project). That's it - all works fine - the command getter is called, and the command is executed when you click the button.

Databinding a class with boolean and text to XMAL

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

Categories

Resources