Change content of UserControl from Page code behind - c#

So I have a user control:
<StackPanel Orientation="Vertical"
Margin="10">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Stretch"
Margin="10">
<TextBlock Text="{x:Bind FileName, Mode=OneTime}"
HorizontalAlignment="Left"/>
<TextBlock Text="{x:Bind DownloadSpeed, Mode=OneWay}"
HorizontalAlignment="Right"/>
</StackPanel>
<ProgressBar Name="PbDownload"
HorizontalAlignment="Stretch" />
<TextBlock Text="{x:Bind DownloadCompletePercent, Mode=OneWay}"/>
</StackPanel>
User control code behind:
public sealed partial class UCDownloadCard : UserControl
{
public UCDownloadCard()
{
this.InitializeComponent();
}
public string FileName { get; set; }
public string DownloadSpeed { get; set; }
public string DownloadCompletePercent { get; set; }
}
What I am trying to do is to show file download status using this user control. Whenever a new download is started, I want to programmatically add a new user control and then update the values in it as the download happens.
Currently I am doing something like this:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
public CancellationTokenSource CancellationTokenSource { get; set; }
public List<DownloadOperation> ActiveDownloads { get; set; } = new List<DownloadOperation>();
public List<UCDownloadCard> AddedCards { get; set; } = new List<UCDownloadCard>();
private async Task HandleDownloadAsync(DownloadOperation downloadOperation, CancellationToken cancellationToken = new CancellationToken())
{
ActiveDownloads.Add(downloadOperation);
...
...
try
{
AddDownloadProgressCard();
await downloadOperation.StartAsync().AsTask(CancellationTokenSource.Token, progressCallback);
}
finally
{
...
...
}
}
private void AddDownloadProgressCard()
{
var card = new UCDownloadCard
{
Name = $"Card{AddedCards.Count}",
FileName = "Filename.pdf",
DownloadCompletePercent = "0% completed",
DownloadSpeed = "0 KB/s"
};
AddedCards.Add(card);
OutputArea.Children.Add(card);
}
private void DownloadProgressChanged(DownloadOperation downloadOperation)
{
var downloadPercent = 100 * ((double)downloadOperation.Progress.BytesReceived / (double)downloadOperation.Progress.TotalBytesToReceive);
this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
{
AddedCards[0].DownloadCompletePercent = downloadPercent.ToString();
Debug.WriteLine($"Updating Progress: {downloadPercent}%");
});
}
}
I am able to add the UserControl to the OutputArea but the values in it are not updating. But I am sure that the AddedCards[0].DownloadCompletePercent = downloadPercent.ToString(); is being executed multiple times because the Debug.WritLine just below it is actually printing to the output window.
How can I update the values in the UserControl ?

Firstly, you should change your UserControl with x:Bind Mode=TwoWay.See {x:Bind} markup extension for more details.
Then you should implement the INotifyPropertyChanged interface and implement the PropertyChanged event. The code you can refer the PropertyChanged event.
Here is a simple sample, you can have a reference.
UserControl.xaml,
<StackPanel Orientation="Vertical"
Margin="10">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Stretch"
Margin="10">
<TextBlock Text="{x:Bind DownloadCompletePercent, Mode=TwoWay}"/>
</StackPanel>
UserControl.xaml.cs,
public sealed partial class UCDownloadCard : UserControl, INotifyPropertyChanged
{
public UCDownloadCard()
{
this.InitializeComponent();
}
private string downloadCompletePercent;
public string DownloadCompletePercent
{
get
{
return downloadCompletePercent;
}
set
{
downloadCompletePercent = value;
RaisePropertyChanged("DownloadCompletePercent");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
Then you can add this UserControl and update its downloadCompletePercent,
In the MainPage.xaml.cs,
private void DownloadProgress(DownloadOperation obj)
{
BackgroundDownloadProgress currentProgress = obj.Progress;
double percent;
if (currentProgress.TotalBytesToReceive > 0)
{
percent = currentProgress.BytesReceived * 100 / currentProgress.TotalBytesToReceive;
Debug.WriteLine(percent);
uCDownloadCard.DownloadCompletePercent = percent.ToString();
}
}
UCDownloadCard uCDownloadCard;
private void Button_Click_2(object sender, RoutedEventArgs e)
{
uCDownloadCard = new UCDownloadCard();
MainPagePanel.Children.Add(uCDownloadCard);
}

Related

Prevent WPF ViewModel from creating new instance when navigating to other views

I am attempting to prevent my application from deleting a view and then creating a new one each time it's navigated around. I have a dashboard that will run a test program, if I select the settings view, then back to the dashboard, it has deleted the running test and initialized a new view. I need to keep the same view instance alive so that the test can continue to run while the user navigates to the settings view and back again but I cant exactly figure out how to successfully do that. I have attempted making the instance static but that doesn't seem to make a difference.
MainViewModel
class MainVM : ViewModelBase
{
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set { _currentView = value; OnPropertyChanged(); }
}
public ICommand DashboardCommand { get; set; }
public ICommand SettingsCommand { get; set; }
public static DashboardVM DashboardInstance { get; } = new DashboardVM();
public static SettingsVM SettingsInstance { get; } = new SettingsVM();
private void Dashboard(object obj) => CurrentView = DashboardInstance;
private void Settings(object obj) => CurrentView = SettingsInstance;
public MainVM()
{
DashboardCommand = new RelayCommand(Dashboard);
SettingsCommand = new RelayCommand(Settings);
// Startup Page
CurrentView = DashboardInstance;
}
}
ViewModelBase
public partial class ViewModelBase : ObservableObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
public void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
MainView - Navigation
<!-- Navigation Panel -->
<Grid HorizontalAlignment="Left" Width="76">
<Border Background="#3D5A8A" CornerRadius="10,0,0,10" />
<StackPanel Height="1200" Width="76">
<!-- Dashboard Button -->
<nav:Button Style="{StaticResource NavButton_Style}"
Command="{Binding DashboardCommand}"
IsChecked="True">
<Grid>
<Image Source="Images/dash_black_50.png"
Style="{StaticResource NavImage_Style}" />
<TextBlock Text="Dashboard"
Style="{StaticResource NavText_Style}" />
</Grid>
</nav:Button>
<!-- Settings Button -->
<nav:Button Style="{StaticResource NavButton_Style}"
Command="{Binding SettingsCommand}">
<Grid>
<Image Source="Images/gear_black_50.png"
Style="{StaticResource NavImage_Style}" />
<TextBlock Text="Settings"
Style="{StaticResource NavText_Style}" />
</Grid>
</nav:Button>
</StackPanel>
</Grid>
DashboardVM
class DashboardVM : ViewModelBase
{
enum TestItemStatus
{
Reset,
Queued,
InProgress,
Pass,
Fail
}
private readonly PageModel _pageModel;
private string _StartButtonText,
_WaveRelayEthernetText;
private bool isTestRunning;
public DashboardVM()
{
_pageModel = new PageModel();
_StartButtonText = "Start Test";
_WaveRelayEthernetText = string.Empty;
StartButtonCommand = new RelayCommand(o => StartButtonClick("StartButton"));
}
#region Text Handlers
public string StartButtonText
{
get { return _StartButtonText; }
set { _StartButtonText = value; NotifyPropertyChanged("StartButtonText"); }
}
public string WaveRelayEthernetText
{
get { return _WaveRelayEthernetText; }
set { _WaveRelayEthernetText = value; NotifyPropertyChanged("WaveRelayEthernetText"); }
}
#endregion
private bool TestRunning
{
get { return isTestRunning; }
set { isTestRunning = value;
if (isTestRunning) { StartButtonText = "Stop Test"; }
else { StartButtonText = "Start Test";
ResetTestItems();
}
NotifyPropertyChanged("TestRunning");
}
}
public ICommand StartButtonCommand { get; set; }
private void StartButtonClick(object sender)
{
if(TestRunning)
{
TestRunning = false;
}
else
{
SetTestItemsToQueued();
MessageBox.Show("Please plug in Tube 1");
// Start program.
TestRunning = true;
WaveRelayEthernetText = TestItemStatusEnumToString(TestItemStatus.InProgress);
}
}
private string TestItemStatusEnumToString(TestItemStatus temp)
{
if (temp == TestItemStatus.Reset) { return string.Empty; }
else if (temp == TestItemStatus.Queued) { return "Queued"; }
else if (temp == TestItemStatus.InProgress) { return "In Progress"; }
else if (temp == TestItemStatus.Pass) { return "Pass"; }
else if (temp == TestItemStatus.Fail) { return "Fail"; }
else { return string.Empty; }
}
private void SetTestItemsToQueued()
{
WaveRelayEthernetText = TestItemStatusEnumToString(TestItemStatus.Queued);
}
private void ResetTestItems()
{
WaveRelayEthernetText = TestItemStatusEnumToString(TestItemStatus.Reset);
}
}
Image for reference:
My Issue was in the App.xaml, I link a DataTemplate file like this:
<ResourceDictionary Source="Utilities/DataTemplate.xaml" />
Inside the data template, I had this code that linked the views to the view models.
<ResourceDictionary [...]">
<DataTemplate DataType="{x:Type vm:DashboardVM}">
<view:Dashboard />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SettingsVM}">
<view:Settings />
</DataTemplate>
</ResourceDictionary>
I changed that code to link the two to this:
<ResourceDictionary [...]>
<view:Dashboard x:Key="DashboardViewKey"/>
<view:Settings x:Key="SettingsViewKey"/>
<DataTemplate DataType="{x:Type vm:DashboardVM}">
<ContentControl Content="{StaticResource DashboardViewKey}" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SettingsVM}">
<ContentControl Content="{StaticResource SettingsViewKey}" />
</DataTemplate>
</ResourceDictionary>
I am now receiveing the expected behavior of being able to navigate without the Dashboard constructor being called, thus the view does not destory and recreate.
I hope someone else finds this useful.

How to get property and call command from different ViewModel with mvvm pattern

I have a ViewModel with all the properties that i will need in every sub ViewModel.
It's the first time i try to split commands and viewmodel to multiple files. Last time everything was in the same ViewModel and it was a pain to work with it. Everything shows up as expected but i want to find a way to pass the same data in every viewmodel.
From my GetOrdersCommand, i want to get the HeaderViewModel.SelectedSource property. I didn't find any way to do it without getting a null return or loosing the property data...
I would like to call my GetOrdersCommand from HeaderView button too.
Any tips how i can achieve this ? Perhaps, my design is not good for what i'm trying to do ?
MainWindow.xaml
<views:HeaderView Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" DataContext="{Binding HeaderViewModel}" LoadHeaderViewCommand="{Binding LoadHeaderViewCommand}"/>
<TabControl TabStripPlacement="Bottom" Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="2">
<TabItem Header="General">
</TabItem>
<TabItem Header="Orders">
<views:OrderView DataContext="{Binding OrderViewModel}" GetOrdersCommand="{Binding GetOrdersCommand}"/>
</TabItem>
</TabControl>
HeaderView.xaml
<DockPanel>
<ComboBox DockPanel.Dock="Left" Width="120" Margin="4" VerticalContentAlignment="Center" ItemsSource="{Binding SourceList}" SelectedItem="{Binding SelectedSource}" DisplayMemberPath="SourceName"/>
<Button x:Name="btnTest" HorizontalAlignment="Left" DockPanel.Dock="Left" Margin="4" Content="Test"/>
</DockPanel>
HeaderView.xaml.cs
public partial class OrderView : UserControl
{
public ICommand GetOrdersCommand
{
get { return (ICommand)GetValue(GetOrdersCommandProperty); }
set { SetValue(GetOrdersCommandProperty, value); }
}
public static readonly DependencyProperty GetOrdersCommandProperty =
DependencyProperty.Register("GetOrdersCommand", typeof(ICommand), typeof(OrderView), new PropertyMetadata(null));
public OrderView()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
if (GetOrdersCommand != null)
{
GetOrdersCommand.Execute(this);
}
}
}
MainViewModel.cs
private OrderViewModel orderViewModel;
public OrderViewModel OrderViewModel { get; set; } // Getter, setter with OnPropertyChanged
private HeaderViewModel headerViewModel;
public HeaderViewModel HeaderViewModel { get; set; } // Getter, setter with OnPropertyChanged
public MainViewModel()
{
HeaderViewModel = new HeaderViewModel();
OrderViewModel = new OrderViewModel();
}
HeaderViewModel.cs
public ICommand LoadHeaderViewCommand { get; set; }
public HeaderViewModel()
{
LoadHeaderViewCommand = new LoadHeaderViewCommand(this);
}
GetOrdersCommand.cs
public class GetOrdersCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private readonly OrderViewModel _orderViewModel;
public GetOrdersCommand(OrderViewModel orderViewModel)
{
_orderViewModel = orderViewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
/* Build Order List according to HeaderViewModel.SelectedSource */
_orderViewModel.Orders = new ObservableCollection<Order>()
{
new Order { ID = 1, IsReleased = false, Name = "Test1"},
new Order { ID = 2, IsReleased = true, Name = "Test2"},
};
}
}
Thanks guys ! I moved my commands to their owning ViewModel as suggested.
I tried MVVVM Light Tools and found about Messenger Class.
I used it to send my SelectedSource (Combobox from HeaderView) from HeaderViewModel to OrderViewModel. Am i suppose to use Messenger class like that ? I don't know, but it did the trick!!!
I thought about moving GetOrdersCommand to OrderViewModel, binding my button command to OrderViewModel, binding SelectedSource as CommandParameter but i didn't know how i was suppose to RaiseCanExecuteChanged when HeaderViewModel.SelectedSource changed... Any advice?
MainWindow.xaml
<views:HeaderView DataContext="{Binding Source={StaticResource Locator}, Path=HeaderVM}" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2"/>
<TabControl TabStripPlacement="Bottom" Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="2">
<TabItem Header="General">
</TabItem>
<TabItem Header="Orders">
<views:OrderView DataContext="{Binding Source={StaticResource Locator}, Path=OrderVM}"/>
</TabItem>
</TabControl>
OrderViewModel.cs
private ObservableCollection<Order> _orders;
public ObservableCollection<Order> Orders
{
get { return _orders; }
set
{
if (_orders != value)
{
_orders = value;
RaisePropertyChanged(nameof(Orders));
}
}
}
public OrderViewModel()
{
Messenger.Default.Register<Source>(this, source => GetOrders(source));
}
private void GetOrders(Source source)
{
if (source.SourceName == "Production")
{
Orders = new ObservableCollection<Order>(){
new Order { ID = 1, IsReleased = false, Name = "Production 1" }
};
}
else
{
Orders = new ObservableCollection<Order>(){
new Order { ID = 2, IsReleased = true, Name = "Test 1" }
};
}
}
Part of HeaderViewModel.cs
private Source _SelectedSource;
public Source SelectedSource
{
get { return _SelectedSource; }
set
{
if (_SelectedSource != value)
{
_SelectedSource = value;
RaisePropertyChanged(nameof(SelectedSource));
GetOrdersCommand.RaiseCanExecuteChanged();
}
}
}
private RelayCommand _GetOrdersCommand;
public RelayCommand GetOrdersCommand
{
get
{
if (_GetOrdersCommand == null)
{
_GetOrdersCommand = new RelayCommand(GetOrders_Execute, GetOrders_CanExecute);
}
return _GetOrdersCommand;
}
}
private void GetOrders_Execute()
{
Messenger.Default.Send(SelectedSource);
}
private bool GetOrders_CanExecute()
{
return SelectedSource != null ? true : false;
}

Why this picker is not selecting an item as expected?

I'm trying to do a picker that loads ItemSource from a List and depending on an external event, change its SelectedIndex based on Local.id, but what I've been trying so far didn't works.
C# code:
public class Local
{
public string cidade { get; set; }
public int id { get; set; }
}
public int CidadeSelectedIndex{ get; set; }
string jsonCidades;
public async void CarregaCidades()
{
try
{
using (WebClient browser = new WebClient())
{
Uri uriCidades = new Uri("xxxxxxx.php");
jsonCidades = await browser.DownloadStringTaskAsync(uriCidades);
}
var ListaCidades = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Local>>(jsonCidades);
PickerCidades.ItemsSource = ListaCidades;
}
catch (Exception)
{
throw;
}
}
//In some moment of the execution, this code is called:
Local localizacao = JsonConvert.DeserializeObject<Local>(json);
if (localizacao.GetType().GetProperty("id") != null)
{
/*CidadeSelectedItem = localizacao;
I tried that before with SelectedItem="{Binding CidadeSelectedItem, Mode=TwoWay}" */
CidadeSelectedIndex = localizacao.id; // now trying this
}
Before I was trying to bind using ItemDisplayBinding="{Binding ListaCidades.cidade, Mode=OneWay}" but since it was not working I start to use ItemSources=ListaCidades
My XAML code:
<Picker x:Name="PickerCidades"
SelectedIndex="{Binding CidadeSelectedIndex, Mode=TwoWay}"
Grid.Column="1" Grid.Row="0"
SelectedIndexChanged="PickerCidades_SelectedIndexChanged">
</Picker>
I think it's not working because I'm setting the items using ItemsSource. I think I need to bind it using xaml. Would be nice have some help.
Do you want to achieve the result like following GIF?
My xaml layout like following code.
<StackLayout>
<!-- Place new controls here -->
<Picker x:Name="PickerCidades"
ItemsSource="{ Binding locals}"
SelectedIndex="{Binding CidadeSelectedIndex, Mode=TwoWay}"
ItemDisplayBinding="{Binding cidade}"
Grid.Column="1" Grid.Row="0"
SelectedIndexChanged="PickerCidades_SelectedIndexChanged">
</Picker>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Text="CidadeSelectedIndex: " Grid.Column="0" Grid.Row="0"/>
<Label Text="{Binding CidadeSelectedIndex}" Grid.Column="1" Grid.Row="0"/>
</Grid>
</StackLayout>
Layout background code.
public partial class MainPage : ContentPage
{
MyViewModel myViewModel;
public MainPage()
{
InitializeComponent();
myViewModel= new MyViewModel();
BindingContext = myViewModel;
}
private void PickerCidades_SelectedIndexChanged(object sender, EventArgs e)
{
var picker = (Picker)sender;
int selectedIndex = picker.SelectedIndex;
myViewModel.CidadeSelectedIndex = selectedIndex;
}
}
MyViewMode code.I use static data for testing. You can achieve the INotifyPropertyChanged interface to change dynamically.
public class MyViewModel : INotifyPropertyChanged
{
int _cidadeSelectedIndex=1;
public int CidadeSelectedIndex
{
set
{
if (_cidadeSelectedIndex != value)
{
_cidadeSelectedIndex = value;
OnPropertyChanged("CidadeSelectedIndex");
}
}
get
{
return _cidadeSelectedIndex;
}
}
public ObservableCollection<Local> locals { get; set; }
public MyViewModel()
{
locals = new ObservableCollection<Local>();
locals.Add(new Local() { cidade= "xxx0" , id= 0 });
locals.Add(new Local() { cidade = "xxx1", id = 1 });
locals.Add(new Local() { cidade = "xxx2", id = 2 });
locals.Add(new Local() { cidade = "xxx3", id = 3 });
locals.Add(new Local() { cidade = "xxx4", id = 4 });
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
If the goal is to change the User Interface from code you need to have a ViewModel that implements INotifyPropertyChanged (or inherits from a base that does). Then instead of SelectedIndex bound property being a simple get; set as below it fires off the PropertyChanged event.
public int CidadeSelectedIndex{ get; set; }
Needs to fire notification event. Something along these lines
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private int _cidadeSelectedIndex;
public int CidadeSelectedIndex
{
get => _cidadeSelectedIndex;
set {
_cidadeSelectedIndex = value;
NotifyPropertyChanged();
}
}
}

Binding Command in TemplatedColumn in RadDataGrid UWP

I am developing a UWP application and I need to show data in RadDataGrid control of Telerik. In one scenario I need to show data using TemplatedColumn and bind commands to controls placed inside its DataTemplate but command are not getting triggered in ViewModel but when I attach event to these controls events get triggered in code behind.
Here is the code:
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="Loaded">
<Core:CallMethodAction MethodName="LoadData"
TargetObject="{Binding}" />
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
<Grid x:Name="gdRoot">
<telerikGrid:RadDataGrid ItemsSource="{x:Bind AvailableVM.PickListItems,Mode=OneWay}"
Background="{StaticResource GridLinesBrush}"
SelectionUnit="Cell"
GridLinesBrush="{StaticResource GridLinesBrush}"
AlternateRowBackground="{StaticResource AlternateRowBackground}"
AutoGenerateColumns="False"
ScrollViewer.VerticalScrollBarVisibility="Hidden">
<telerikGrid:RadDataGrid.Columns>
<telerikGrid:DataGridTemplateColumn Header="Assign"
SizeMode="Auto">
<telerikGrid:DataGridTemplateColumn.CellContentTemplate>
<DataTemplate>
<Button Background="Transparent"
Command="{Binding DataContext.ListSelectedCommand, ElementName=gdRoot}"/>
</DataTemplate>
</telerikGrid:DataGridTemplateColumn.CellContentTemplate>
</telerikGrid:DataGridTemplateColumn>
</telerikGrid:RadDataGrid.Columns>
</telerikGrid:RadDataGrid>
</Grid>
here is the ViewModel Code:
private ICommand _listSelectedCommand;
public ICommand ListSelectedCommand
{
get { return _listSelectedCommand; }
set { Set(nameof(ListSelectedCommand), ref _listSelectedCommand,value); }
}
public void LoadData()
{
InitializeCommands();
}
private void InitializeCommands()
{
ListSelectedCommand= new RelayCommand(()=>
{
});
}
What could be the possible reason behind this.
The most possible reason for the command does't work should be you didn't bind the command correctly. Since your code snippet is not the full, the incorrect binding may be caused by many reasons. Here is a small demo I tested which can work on my side you may reference.
XAML:
<telerikGrid:RadDataGrid ItemsSource="{x:Bind AvailableVM.PickListItems,Mode=OneWay}"
Background="White"
SelectionUnit="Cell"
GridLinesBrush="Pink"
AlternateRowBackground="Azure"
AutoGenerateColumns="False"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
x:Name="radgrid">
<telerikGrid:RadDataGrid.Columns>
<telerikGrid:DataGridTextColumn PropertyName="Country"/>
<telerikGrid:DataGridTextColumn PropertyName="City"/>
<telerikGrid:DataGridTemplateColumn Header="Assign" SizeMode="Auto">
<telerikGrid:DataGridTemplateColumn.CellContentTemplate>
<DataTemplate x:DataType="local:DataTest">
<Button Background="Transparent" Command="{x:Bind ListSelectedCommand }" Content="command testing" />
</DataTemplate>
</telerikGrid:DataGridTemplateColumn.CellContentTemplate>
</telerikGrid:DataGridTemplateColumn>
</telerikGrid:RadDataGrid.Columns>
</telerikGrid:RadDataGrid>
Code behind:
public ViewModel AvailableVM { get; set; }
public MainPage()
{
this.InitializeComponent();
AvailableVM = new ViewModel();
}
public class ViewModel
{
public void Testmethod()
{
}
public ObservableCollection<DataTest> PickListItems { get; set; }
public ViewModel()
{
PickListItems = new ObservableCollection<DataTest>()
{
new DataTest { Country = "Brazil", City = "Caxias do Sul", ListSelectedCommand = new RelayCommand(()=>{ })},
new DataTest { Country = "Ghana", City = "Wa", ListSelectedCommand = new RelayCommand(Testmethod)},
new DataTest { Country = "Brazil", City = "Fortaleza"}
};
}
}
public class DataTest
{
public string City { get; set; }
public string Country { get; set; }
public ICommand ListSelectedCommand { get; set; }
}
class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private Action _action;
public RelayCommand(Action action)
{
this._action = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
this._action();
}
}
By the way, the CellContentTemplate may have influences on binding. Tried to bind on the above way. Also the RadDataGrid has its own commands you can refer if there is one suit your scenario.

Access collection through MVVM Command

In my ViewModel i Have base Card class and Deck class which contain Observable Collection of Cards. Here is how it is bound in XAML
<GridView ItemsSource="{Binding DeckCollection}" IsItemClickEnabled="True" Grid.Row="0">
<GridView.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Path=??}"
CommandParameter=??
<Button.Content>
<Grid>
<Image
Source="{Binding ImagePath}"
Stretch="None"/>
</Grid>
</Button.Content>
</Button>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
Here are my classes
class Deck
{
private ObservableCollection<Card> _deckCollection = new ObservableCollection<Card>();
public ObservableCollection<Card> DeckCollection
{
get { return _deckCollection; }
set { _deckCollection = value; }
}
public Deck()
{
ActionCommand = new MyCommand();
ActionCommand.CanExecuteFunc = obj => true;
ActionCommand.ExecuteFunc = AddToList;
}
public void AddToList(object parameter)
{
var clickedCard = this;
//add Card to list which in this case is not possible
//DeckCollection.Add(this) ?
}
}
class Card
{
public String Name { get; set; }
public int Cost { get; set; }
public String ImagePath { get; set; }
public MyCommand ActionCommand { get; set; }
}
And also MyCommand class
public class MyCommand : ICommand
{
public Predicate<object> CanExecuteFunc { get; set; }
public Action<object> ExecuteFunc { get; set; }
public bool CanExecute(object parameter)
{
return CanExecuteFunc(parameter);
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
ExecuteFunc(parameter);
}
}
I have made suggested changes but right now ActionCommand is not visible within collection, as only properties that belong to Card class can be bound.
EDIT:I have changed my XAML file for following but got some errors
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Deck}, Path=ActionCommand}}">
The property 'AncestorType' was not found in type 'RelativeSource'.
The property 'Path' was not found in type 'RelativeSource'.
The member "AncestorType" is not recognized or is not accessible.
The member "Path" is not recognized or is not accessible.
Unknown member 'AncestorType' on element 'RelativeSource'
Unknown member 'Path' on element 'RelativeSource'
Please help
If you want to have button which adds new items to your collection, I think something like that can be the solution.
In XAML:
<GridView ItemsSource="{Binding DeckCollection}" IsItemClickEnabled="True" Grid.Row="0">
<GridView.ItemTemplate>
<DataTemplate>
<Button>
<Button.Content>
<Grid>
<Image Source="{Binding ImagePath}"
Stretch="None"/>
</Grid>
</Button.Content>
</Button>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
<!-- public property located in Deck class -->
<Button Command="{Binding AddItemCommand}" Content="Add Item"/>
In C#:
class Deck, INotifyPropertyChanged /*custom implementation depends on .NET version, in my case its .NET3.5*/
{
private ObservableCollection<Card> _deckCollection = new ObservableCollection<Card>();
public ObservableCollection<Card> DeckCollection
{
get { return _deckCollection; }
set { _deckCollection = value;
OnPropertyChanged(() => DeckCollection); }
}
// your Add command
public ICommand AddItemCommand { get { return new MyCommand(AddToList); } }
private void AddToList(object parameter)
{
DeckCollection.Add(new Card());
}
public Deck() { }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged<T>(Expression<Func<T>> expression)
{
if (PropertyChanged == null) return;
var body = (MemberExpression)expression.Body;
if (body != null) PropertyChanged.Invoke(this, new PropertyChangedEventArgs(body.Member.Name));
}
}
The main thing in this situation is that you cannot have the add button inside the collection.

Categories