Winui3 Desktop; ObservableCollection, updating UI when property changes? Updating from different thread - c#

This is a small test app to try and figure this problem out from my main app. I'll paste the code first.
XAML:
<Window
x:Class="ThreadTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ThreadTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border BorderBrush="Black" BorderThickness="2" Grid.Row="0"/>
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="0">
<Button Click="{x:Bind ViewModel.AddPosition}" Content="Add To List" Margin="5"/>
<TextBlock Text="{x:Bind ViewModel.OutputString, Mode=OneWay}" Margin="5"/>
<ListView ItemsSource="{x:Bind ViewModel.PositionCollection, Mode=OneWay}" Margin="5">
<ListView.HeaderTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border BorderBrush="BlueViolet" BorderThickness="0,0,0,1">
<TextBlock Text="ID" Margin="5,0,0,0" FontWeight="Bold"/>
</Border>
<Border Grid.Column="1" BorderBrush="BlueViolet" BorderThickness="0,0,0,1">
<TextBlock Text="Place" Margin="5,0,0,0" FontWeight="Bold"/>
</Border>
</Grid>
</DataTemplate>
</ListView.HeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:PositionModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Path=ID, Mode=OneWay}"/>
<TextBlock Grid.Column="1" Text="{x:Bind Path=Place, Mode=OneWay}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
</Window>
ViewModel (MainViewModel.cs):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using Microsoft.UI.Dispatching;
using Windows.UI.Core;
using Windows.ApplicationModel;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ThreadTest {
public class MainViewModel : BindableBase, INotifyPropertyChanged {
private String outputString = "Empty";
public MainViewModel() {
}
public String OutputString {
get { return outputString; }
set { SetProperty(ref outputString, value); }
}
private Random _random = new Random();
private int _id = 0;
private ObservableCollection<PositionModel> _positioncollection = new();
public ObservableCollection<PositionModel> PositionCollection {
get { return _positioncollection; }
set { SetProperty(ref _positioncollection, value); }
}
public async void AddPosition() {
Progress<PositionModel> progress = new();
progress.ProgressChanged += Progress_ProgressChanged;
// Increase id for each new position added.
_id++;
// Setup/
var _position = new PositionModel {
ID = _id,
Place = _random.Next(1, 1000), // Get a random starting point.
};
PositionCollection.Add(_position);
PositionsClass positionsClass = new(ref _position, progress);
await Task.Run(() => { positionsClass.Start(); });
}
private void Progress_ProgressChanged(object sender, PositionModel e) {
// This is so I can see that the thread is actually running.
OutputString = Convert.ToString(e.Place);
}
}
}
BindableBase.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace ThreadTest {
public class BindableBase : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T originalValue, T newValue, [CallerMemberName] string propertyName = null) {
if (Equals(originalValue, newValue)) {
return false;
}
originalValue = newValue;
OnPropertyChanged(propertyName);
return true;
}
}
}
PositionModel.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace ThreadTest {
public class PositionModel {
/*
//Impliment INotifyPropertyChanged up above if using this.
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T originalValue, T newValue, [CallerMemberName] string propertyName = null) {
if (Equals(originalValue, newValue)) {
return false;
}
originalValue = newValue;
OnPropertyChanged(propertyName);
return true;
}
private int _id = 0;
public int ID {
get { return _id; }
set { SetProperty(ref _id, value); }
}
private int _place = 0;
public int Place {
get { return _place; }
set { SetProperty(ref _place, value); }
}
*/
public int ID { get; set; }
public int Place { get; set; }
}
}
PositionsClass.cs:
using Microsoft.UI.Dispatching;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadTest {
public class PositionsClass {
private IProgress<PositionModel> _progress;
private PositionModel _position;
public PositionsClass(ref PositionModel position, IProgress<PositionModel> progress) {
_progress = progress;
_position = position;
}
public void Start() {
StartFakePosition();
}
private void StartFakePosition() {
// Just using a quick loop to keep the numbers going up.
while (true) {
_position.Place++;
// Send position back.
_progress.Report(_position);
Thread.Sleep(100);
}
}
}
}
So basically you'll click the 'add to list' button which will then create the PositionsClass positionsClass, create and populate the PositionModel _position, create an ObservableCollection PositionCollection (bound to listview on xaml) then spin off the class into it's own thread. The class will get _position and increase its .Place, then progress.report the _position back to main thread.
Now I'm trying to figure out how to get the PositionCollection (of ObservableCollection) to update the lisview ui. I subscribed to progress.ProgressChanged and update the OutputString just to make sure the class is actually running and incrementing which does work.
I've tried various things I've found on the web, including different inherited ObversableCollection methods, none of which work or I missunderstood them.
I thought implementing an INotifyPropertyChange on PositionModel.cs itself would work (the commented out code), but doing so brings up a cross thread error. I imagine it's because positionsClass on a seperate thread is updating .Place which is causing the cross thread error?
Can anyone help explain how to get the ObservableCollection to update the ui when it's property changes in my example above? Thanks! In my main app, I'll be updating a lot of properties on a separate thread, rather than just the 2 in this example. Which is why I thought it'd be easier to just send the whole model back in a progress.report.

I think I've figured it out. First I enable the INotifyProperty on PositionModel.cs code above (the commented out part). Then I add:
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
public CoreDispatcher Dispatcher { get; }
To the MainViewModel, and modify AddPosition to:
public async void AddPositionDispatcher() {
// Increase id for each new position added.
_id++;
// Setup/
var _position = new PositionModel {
ID = _id,
Place = _random.Next(1, 1000), // Get a random starting point.
};
PositionCollection.Add(_position);
PositionsClassDispatcher positionsClassDispatcher = new(_position, _dispatcherQueue);
await Task.Run(() => { positionsClassDispatcher.Start(); });
}
Where I send a DispatcherQueue to the new modified PositionsClassDispatcher.cs:
using Microsoft.UI.Dispatching;
using System.Threading;
namespace ThreadTest {
internal class PositionsClassDispatcher {
private PositionModel _position;
DispatcherQueue _queue;
public PositionsClassDispatcher(PositionModel position, DispatcherQueue dispatcherQueue) {
_queue = dispatcherQueue;
_position = position;
}
public void Start() {
StartFakePosition();
}
private void StartFakePosition() {
// Just using a quick loop to keep the numbers going up.
while (true) {
_queue.TryEnqueue(() => {
_position.Place++;
});
Thread.Sleep(100);
}
}
}
}
Which will take the DispatcherQueue and use TryEnqueue to update _position.Place. ObvservableCollection now properly updates UI when properties are updated. Also update the XAML to use the new AddPositionDispatcher().
Also, having to use DispatcherQueue rather than Dispatcher as WinUI3 seems to not have have Dispatcher anymore.
Window.Dispatcher Property
Window.Dispatcher may be altered or unavailable in future releases. Use Window.DispatcherQueue instead.
Which has caused quite a few problems trying to figure this problem out, as a lot of info out there is based on Dispatcher rather than DispatcherQueue.
Hope this helps anyone else that runs into the problem.

Related

MAUI not updating UI on MVVM Binding

I have a MAUI application that changes the language on the interface according to a language picker menu. The application was in Xamarin and I am porting it to MAUI: in Xamarin all worked perfectly, but not in MAUI. Basically the UI does not update according to MVVM bindings using onpropertychanged. I am not understanding what is wrong.
Here is my XAML code
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:CustomViews="clr-namespace:HeatLoadApp_MAUI.CustomViews"
NavigationPage.HasNavigationBar="False"
x:Class="HeatLoadApp_MAUI.SettingsPage"
xmlns:ViewModels="clr-namespace:HeatLoadApp_MAUI.ViewModels"
x:DataType="ViewModels:SettingsViewModel">
<ContentPage.Content>
<VerticalStackLayout>
<!--NAVIGATION BAR-->
<CustomViews:CustomNavigationBar Grid.Row="0" TitleText="{Binding Settings}"/>
<Grid RowSpacing="20" Padding="20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!--LANGUAGE SELECTION-->
<Label
x:Name="lblLanguage"
Grid.Row="0"
Grid.Column="0"
Text="{Binding Language}"/>
<Picker
x:Name="pickerLanguage"
Grid.Row="0"
Grid.Column="1"
Title="Select language"
ItemsSource="{Binding LanguageList}"
SelectedIndex="{Binding SelectedIndex}"/>
</Grid>
</VerticalStackLayout>
</ContentPage.Content>
</ContentPage>
Here is my code behind
using HeatLoadApp_MAUI.ViewModels;
namespace HeatLoadApp_MAUI;
public partial class SettingsPage : ContentPage
{
//this is the viewmodel for this page
SettingsViewModel settingsViewModel = new SettingsViewModel();
public SettingsPage()
{
InitializeComponent();
BindingContext = settingsViewModel;
}
}
Here is my ViewModel
using System;
using System.ComponentModel;
using System.Data;
using System.Runtime.CompilerServices;
using HeatLoadApp_MAUI.Utilities;
namespace HeatLoadApp_MAUI.ViewModels
{
public class SettingsViewModel: NotifyPropertyChanged
{
public SettingsViewModel()
{
RefreshLanguagesOnAppearing();
}
public void RefreshLanguagesOnAppearing()
{
Settings = "";
Language = "";
}
private string settings;
public string Settings
{
get { return settings; }
set
{
settings = FilterTranslationDatabase(StaticShareProperties.selectedLanguageIndex);
OnPropertyChanged();
}
}
private string language;
public string Language
{
get { return language; }
set
{
//alternative call for stackoverflow question
language = WorkAroundForStackOverflow();
//standard call
language = FilterTranslationDatabase(StaticShareProperties.selectedLanguageIndex);
OnPropertyChanged();
}
}
public string WorkAroundForStackOverflow()
{
string dummy = null;
if (selectedIndex == 0) dummy = "lingua";
else if (selectedIndex == 1) dummy = "language";
return dummy;
}
private int selectedIndex;
public int SelectedIndex
{
get { return selectedIndex; }
set
{
selectedIndex = value;
StaticShareProperties.selectedLanguageIndex = selectedIndex + 3;
//plus 3 beacuse tranlations start from third column of database
RefreshLanguagesOnAppearing();
}
}
//language list to be shown in picker menu
public List<string> LanguageList
{
get
{
return new List<string> { "Italiano", "English" };
}
}
private string FilterTranslationDatabase(int indexLanguage, [CallerMemberName] string callerMember = "")
{
DataView dataview = App.dtTranslations.DefaultView;
dataview.RowFilter = App.dtTranslations.Columns[2].ToString() + "='" + callerMember.ToString() + "'";
string translation = dataview.ToTable().Rows[0][indexLanguage].ToString();
return translation;
}
}
}
And I have finally my property changed implemented as well
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace HeatLoadApp_MAUI.Utilities
{
public class NotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
FYI: As an alternative to writing your own INotifyPropertyChanged implementor, you can simply inherit from Community Toolkits / MVVMToolkit / ObservableObject:
public class SettingsViewModel: ObservableObject
...

C# WPF UI unresponsive when performing drag drop using user control

I am stuck with a C# wpf drag drop issue. I have created a very simple project that includes a User Control, a couple of classes to hold the data and a form to host multiple copies of the user control (using a bound ItemsControl). When I drag the control onto the form the drag drop is triggered, the observablecollection is updated but the UI doesn't reflect the change and future events don't seem to be working. Rolling over the add item button doesn't even show the rollover effect. Sure I am doing something stupid but I can't seem to see what it is.
Code below (mainly from Microsoft Example)
SimpleDataClass
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DragDropControl.Model
{
public class SimpleDataClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _groupName = string.Empty;
private ObservableCollection<SimpleSubDataClass> _titles = new ObservableCollection<SimpleSubDataClass>();
public string GroupName
{
get { return _groupName; }
set
{
if (_groupName != value)
{
_groupName = value;
RaisePropertyChangedEvent("GroupName");
}
}
}
public ObservableCollection<SimpleSubDataClass> Titles
{
get { return _titles; }
set
{
if (_titles != value)
{
_titles = value;
RaisePropertyChangedEvent("Titles");
}
}
}
private void RaisePropertyChangedEvent(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
SimpleSubDataClass
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DragDropControl.Model
{
public class SimpleSubDataClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _title = string.Empty;
public string Title
{
get { return _title; }
set
{
if (_title != value)
{
_title = value;
RaisePropertyChanged("Title");
}
}
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public SimpleSubDataClass(string title)
{
Title = title;
}
}
}
DDControl - XAML
<UserControl x:Class="DragDropControl.DDControl"
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:local="clr-namespace:DragDropControl"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="CurrentControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Name="txtGroupName" Grid.Row="0" Text="{Binding ElementName=CurrentControl, Path=ThisData.GroupName}"/>
<ListBox Name="lstTitles" Grid.Row="1" ItemsSource="{Binding ElementName=CurrentControl, Path=ThisData.Titles}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Name="lblTitle" Content="{Binding Title}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
DDControl - Code behind
using DragDropControl.Model;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace DragDropControl
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class DDControl : UserControl
{
public static readonly DependencyProperty ThisDataProperty = DependencyProperty.Register( "ThisData",
typeof(SimpleDataClass),
typeof(DDControl),
new PropertyMetadata(new SimpleDataClass()));
public SimpleDataClass ThisData
{
get { return (SimpleDataClass)GetValue(ThisDataProperty); }
set { SetValue(ThisDataProperty, value); }
}
public DDControl()
{
InitializeComponent();
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed)
{
DataObject data = new DataObject(this.ThisData);
DragDrop.DoDragDrop(this, data, DragDropEffects.Move);
}
}
protected override void OnGiveFeedback(GiveFeedbackEventArgs e)
{
base.OnGiveFeedback(e);
if (e.Effects.HasFlag(DragDropEffects.Move))
Mouse.SetCursor(Cursors.Pen);
e.Handled = true;
}
}
}
MainWindow - Xaml
<Window x:Class="DragDropUserControlWithTextBox.MainWindow"
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:local="clr-namespace:DragDropUserControlWithTextBox"
xmlns:ddc="clr-namespace:DragDropControl;assembly=DragDropControl"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<StackPanel Name="stkMain" Background="Gray" Orientation="Horizontal" Drop="stkMain_Drop" AllowDrop="true">
<ItemsControl Name="icColumns" Background="Red">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Name="stkItemsControlPanel" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ddc:DDControl Background="{x:Null}" ThisData="{Binding}"/><!-- MouseMove="DDControl_MouseMove" GiveFeedback="DDControl_GiveFeedback"/>-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Name="btnAddData" Content="Add Data" Click="btnAddData_Click"/>
</StackPanel>
</ScrollViewer>
</Grid>
</Window>
MainWindow - Code behind
using DragDropControl.Model;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
namespace DragDropUserControlWithTextBox
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<SimpleDataClass> _data = new ObservableCollection<SimpleDataClass>();
public MainWindow()
{
InitializeComponent();
CreateTestData();
}
private void CreateTestData()
{
SimpleDataClass tempSDC1 = new SimpleDataClass();
tempSDC1.GroupName = "First Item";
tempSDC1.Titles.Add(new SimpleSubDataClass("Title 1_1"));
tempSDC1.Titles.Add(new SimpleSubDataClass("Title 1_2"));
tempSDC1.Titles.Add(new SimpleSubDataClass("Title 1_3"));
tempSDC1.Titles.Add(new SimpleSubDataClass("Title 1_4"));
tempSDC1.Titles.Add(new SimpleSubDataClass("Title 1_5"));
SimpleDataClass tempSDC2 = new SimpleDataClass();
tempSDC2.GroupName = "Second Item";
tempSDC2.Titles.Add(new SimpleSubDataClass("Title 2_1"));
tempSDC2.Titles.Add(new SimpleSubDataClass("Title 2_2"));
tempSDC2.Titles.Add(new SimpleSubDataClass("Title 2_3"));
tempSDC2.Titles.Add(new SimpleSubDataClass("Title 2_4"));
tempSDC2.Titles.Add(new SimpleSubDataClass("Title 2_5"));
_data.Add(tempSDC1);
_data.Add(tempSDC2);
this.icColumns.ItemsSource = _data;
}
private void stkMain_Drop(object sender, DragEventArgs e)
{
if (e.Handled == false)
{
if (e.Data.GetDataPresent(typeof(SimpleDataClass)))
{
SimpleDataClass tempData = (SimpleDataClass)e.Data.GetData(typeof(SimpleDataClass));
_data.Add(tempData);
}
e.Effects.HasFlag(DragDropEffects.None);
Mouse.SetCursor(Cursors.Arrow);
e.Handled = true;
}
}
private void btnAddData_Click(object sender, RoutedEventArgs e)
{
SimpleDataClass tempData = new SimpleDataClass();
tempData.GroupName = "Amazing Test";
tempData.Titles.Add(new SimpleSubDataClass("AT_1"));
tempData.Titles.Add(new SimpleSubDataClass("AT_2"));
tempData.Titles.Add(new SimpleSubDataClass("AT_3"));
_data.Add(tempData);
}
}
}
I swapped to using the method described in this WPF tutorial on drag and drop. It still had an issue when you dragged and dropped a user control with a textbox on it (would assume it would be for any control that is hitenabled) where it would create a second instance of the item being dragged but that is pretty easy to work around, just set the enabled state to false when detecting the control is about to be dragged and enable it again when it is dropped. Possibly a hack but one that works.
For drag and drop, create a Behavior and handle the events in the Behavior for the avoiding the UI thread freeze scenarios. Check the below link will be useful for your scenario.
https://www.telerik.com/blogs/adding-drag-and-drop-to-wpf-listboxes-thanks-telerik!

WPF CommandParameter is null despite being bound

I'm stumped on this one. Why would a command parameter be continuously empty despite being bound to a property that has a value.
XAML
<UserControl x:Class="CatalogInterface.ctlMainButtonPanel"
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:local="clr-namespace:CatalogInterface"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="MainButtonPanel">
<UserControl.Resources>
<local:MainButtonPanelViewModel x:Key="ViewModel" />
<!--#region Button Style-->
<!--#region FocusVisual-->
<!--#endregion FocusVisual-->
<!--#region Button-->
<!--#endregion Button-->
<!--#endregion Button Style-->
</UserControl.Resources>
<Grid DataContext="{StaticResource ViewModel}">
<!--/////////////////////////////////////////////////////////////////////////////////-->
<!--////////This is the button that won't pass the CommandParameter//////////////////-->
<!--/////////////////////////////////////////////////////////////////////////////////-->
<Button x:Name="cmdConvertToFBook"
CommandParameter="{Binding SelectedDocument}"
Command="{Binding ConvertToFacebookCommand}"
Content="CONVERT TO FACEBOOK"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Grid.Column="0" Grid.Row="2" />
</UserControl>
View Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace CatalogInterface
{
class MainButtonPanelViewModel : ViewModelBase
{
private Messanger _messanger = Messanger.Set_Messanger();
/////////////////////////////////////////////////////////////////////////////////
////////This is the property I'm binding my commandparameter to//////////////////
/////////////////////////////////////////////////////////////////////////////////
private object _selectedDocument
public object SelectedDocument
{
get { return _selectedDocument; }
set { _selectedDocument = value; }
}
public ConvertToFacebookCommand ConvertToFacebookCommand { get; set; }
public MainButtonPanelViewModel()
{
ConvertToFacebookCommand = new ConvertToFacebookCommand();
_messanger.Register(OnSentMessage, "DirFilesListBox_SelectedDocumentChanged");
}
public void OnSentMessage(object source, MessageEventArgs e)
{
_selectedDocument = e.MessageObject;
}
}
public class ConvertToFacebookCommand : SingleFuncitonBaseCommand
{
public override void ButtonFunction(object parameter)
{
ConvertToFacebookFn.Convert(parameter);
}
public override void NotExacutable(object parameter)
{
ButtonNotExacutable.Report(parameter);
}
}
}
ViewModelBase
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace CatalogInterface
{
public abstract class SingleFuncitonBaseCommand : ICommand
{
private bool _canExecute { get; set; } = true;
public SingleFuncitonBaseCommand()
{
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public virtual bool CanExecute(object parameter)
{
return true;
}
public virtual void Execute(object parameter)
{
if (_canExecute && parameter != null)
{
try
{
_canExecute = false;
ButtonFunction(parameter);
}
finally
{
_canExecute = true;
}
return;
}
NotExacutable(parameter);
}
public abstract void ButtonFunction(object parameter);
public abstract void NotExacutable(object parameter);
}
}
My SelectedDocument property is being set by the OnSentMessage event that updates the property when the user clicks on a File in a list box. I believe the object is a FileInfo object. I can confirm the event is firing properly and the property is being set.
Also if i change my SelectedDocument property to a readonly property and set it to a FileInfo object it then does get passed to the parameter.
Is it possible for my OnSentMessage event and CommandParameter binding to be operating on two different objects?
How can I debug this further?
Thanks All.

Binding data to ListBox in xaml getting data from webservice

I have this code:
using System;
using System.Collections.Generic;
using System.linq;
using System.Text;
using System.Threading.Tasks;
using System.Component-model;
namespace MEO.MODELS
{
public class Claims :INotifyPropertyChanged
{
public Claims()
{
}
private string description;
private string expenseHeaderId;
private string assginedTo;
private bool submitted;
private bool approved;
private bool authorised;
private DateTime updatedDate;
private DateTime createdDate;
private DateTime claimDate;
private DateTime lastModifiedDate;
private string expenseFormType;
public bool Approved
{
get
{
return this.approved;
}
set
{
if (value != this.approved)
{
this.approved = value;
this.NotfiyProperty("Approved");
}
}
}
public string AssignedTo
{
get
{
return this.assginedTo;
}
set
{
if (value != this.assginedTo)
{
this.assginedTo = value;
this.NotfiyProperty("AssignedTo");
}
}
}
public bool Authorised
{
get
{
return this.authorised;
}
set
{
if (value != authorised)
{
this.authorised = value;
this.NotfiyProperty("Authorised");
}
}
}
public bool Submitted
{
get
{
return this.submitted;
}
set
{
if (value != submitted)
{
this.submitted = value;
this.NotfiyProperty("Submitted");
}
}
}
public DateTime ClaimDate
{
get
{
return this.claimDate;
}
set
{
if (value != claimDate)
{
this.claimDate = value;
this.NotfiyProperty("ClaimDate");
}
}
}
public DateTime CreatedDate
{
get
{
return this.createdDate;
}
set
{
if (value != createdDate)
{
this.createdDate = value;
this.NotfiyProperty("CreatedDate");
}
}
}
public DateTime LastModifiedDate
{
get
{
return this.lastModifiedDate;
}
set
{
if (value != lastModifiedDate)
{
this.lastModifiedDate = value;
this.NotfiyProperty("LastModifiedDate");
}
}
}
public DateTime UpdatedDate
{
get
{
return this.updatedDate;
}
set
{
if (value != updatedDate)
{
this.updatedDate = value;
this.NotfiyProperty("UpdatedDate");
}
}
}
public string Description
{
get
{
return this.description;
}
set
{
if (value != this.description)
{
this.description = value;
this.NotfiyProperty("Description");
}
}
}
public string ExpenseFormType
{
get
{
return this.expenseFormType;
}
set
{
if (value != this.expenseFormType)
{
this.expenseFormType = value;
this.NotfiyProperty("ExpenseFormType");
}
}
}
public string ExpenseHeaderId
{
get
{
return this.expenseHeaderId;
}
set
{
if (value != this.expenseHeaderId)
{
this.expenseHeaderId = value;
this.NotfiyProperty("ExpenseHeaderId");
}
}
}
private void NotfiyProperty(string propertyName)
{
PropertyChangedEventHandler propertyChangedEventHandler = this.PropertyChanged;
if (propertyChangedEventHandler != null)
{
propertyChangedEventHandler.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
And the following is the XAML which contains a Listbox.I have to bind the data getting from webservice calling a method called GetFullClaimLinesAsync by passing parameters when page is loaded.
<phone:PhoneApplicationPage
x:Class="MEO.Views.Result"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d"
shell:SystemTray.IsVisible="True">
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Margin="12,17,0,28" Grid.ColumnSpan="2">
<Button Content="My Expenses On line" Background="#00ccff" Grid.Row="5" FontWeight="Bold" Name="head" Margin="-23,0,-13,-98" Foreground="White" BorderThickness="0"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Margin="0,35,24,10" Grid.ColumnSpan="2"/>
<!--<Button Content="Get Full Claims" Background="#00ccff" FontWeight="Bold" x:Name="claim" Click="claim_Click" Margin="67,105,97,-203" Foreground="White" BorderThickness="0" Grid.Row="1" RenderTransformOrigin="0.676,0.469"/>-->
<Grid Margin="0,203,10,-713" Grid.Row="1" >
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="0*"/>
</Grid.ColumnDefinitions>
<ListBox HorizontalAlignment="Left" Name="listbox1" ItemsSource="{Binding}" VerticalAlignment="Top" Height="500" Width="0">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50">
</ColumnDefinition>
<ColumnDefinition Width="50">
</ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Description}" Margin="=3" Grid.Column="0"></TextBlock>
<TextBlock Text="{Binding ExpenseHeaderId}" Margin="=3" Grid.Column="1"></TextBlock>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
</phone:PhoneApplicationPage>
and the following is the code behind file (Result.xaml.cs):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using System.Xml.Linq;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using System.IO;
using System.Net.NetworkInformation;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
using MEO.MODELS;
namespace MEO.Views
{
public partial class Result : PhoneApplicationPage
{
//public Guid rid = new Guid(NavigationContext.QueryString["id"]);
//public string rpswd = NavigationContext.QueryString["password"];
//public DateTime headerFromDate = DateTime.Today;
//public DateTime detaiFromDate = DateTime.Today;
ObservableCollection<Claims> oc = new ObservableCollection<Claims>();
public Result()
{
InitializeComponent();
Guid rid = new Guid("0525466131154515");
string rpswd = "hchdj455mjchdjkch7dc1njj";
DateTime headerFromDate = Convert.ToDateTime("555525545");
DateTime detaiFromDate = Convert.ToDateTime("38635");
LoginService.DXDataMobileSoapClient client = new LoginService.DXDataMobileSoapClient();
client.GetFullClaimLinesCompleted+=client_GetFullClaimLinesCompleted;
client.GetFullClaimLinesAsync(rid, rpswd, "", headerFromDate, detaiFromDate, rid);
}
private void client_GetFullClaimLinesCompleted(object sender, LoginService.GetFullClaimLinesCompletedEventArgs e)
{
try
{
if (e.Error == null)
{
XElement[] array = Enumerable.ToArray<XElement>(e.Result.ReturnedDataTable.Any1.Descendants("ClaimHeadersDT"));
if (Enumerable.Count<XElement>(array) > 0)
{
//ObservableCollection<Claims> oc = new ObservableCollection<Claims>();
for (int i = 0; i < Enumerable.Count<XElement>(array); i++)
{
oc.Add(new Claims()
{
Description = (string)array[i].Element("h_description"),
ExpenseHeaderId =(string)array[i].Element("h_expense_headerID"),
});
}
listbox1.ItemsSource = oc;
}
}
}
catch(Exception ex)
{
throw ex;
}
}
}
}
My problem is that I am getting data from web service i.e. from XML and the data is added to my collection i.e. ObservableCollection oc. But I am unable to bind the oc data to list box. I am getting error in App.xaml like unhandeled exception. The error is not catched in my catch block. However Listbox.Itemssource has data, containing 603 items.
i think you get this problem because the listbox use a Model as Datatemplate, what you getting from the webserver is xml data and turning to a string, i did something like that before, and goas like that...
private void client_GetFullClaimLinesCompleted(class.......)
{
oc.ItemsSource = e.Result;
}
as you said, you're getting 603 results but they can't be recognized on DataTemplate Model

How to trigger a new validation when a textbox gets enabled?

In a wpf dialog window I have a checkbox that enables and disables a textbox. The textbox has ValidatesOnDataErrors set to True. Via IDataErrorInfo I check the value of this textbox only, if the checkbox is checked.
My problem is that if the user checks the checkbox there is no new validation on textbox performed and therefor I dont get this red frame indicating an error.
For demonstration here is a small sample:
<Window x:Class="Validation.ValidationWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ValidationWindow" Height="300" Width="300">
<DockPanel LastChildFill="False">
<CheckBox DockPanel.Dock="Top" IsChecked="{Binding InputAllowed}">Input allowed</CheckBox>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Label>InputValue</Label>
<TextBox Text="{Binding InputValue, ValidatesOnDataErrors=True}" IsEnabled="{Binding InputAllowed}" Width="50"/>
</StackPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Label>InputValue2</Label>
<TextBox Text="{Binding InputValue2, ValidatesOnDataErrors=True}" Width="50"/>
</StackPanel>
<Button DockPanel.Dock="Bottom" Click="OnOk">Ok</Button>
</DockPanel>
</Window>
code behind:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Validation
{
/// <summary>
/// Interaction logic for ValidationWindow.xaml
/// </summary>
public partial class ValidationWindow : Window, IDataErrorInfo
{
public bool InputAllowed
{
get { return (bool)GetValue(InputAllowedProperty); }
set { SetValue(InputAllowedProperty, value); }
}
public static readonly DependencyProperty InputAllowedProperty =
DependencyProperty.Register("InputAllowed", typeof(bool), typeof(ValidationWindow), new PropertyMetadata(false));
public int InputValue
{
get { return (int)GetValue(InputValueProperty); }
set { SetValue(InputValueProperty, value); }
}
public static readonly DependencyProperty InputValueProperty =
DependencyProperty.Register("InputValue", typeof(int), typeof(ValidationWindow), new PropertyMetadata(0));
public int InputValue2
{
get { return (int)GetValue(InputValue2Property); }
set { SetValue(InputValue2Property, value); }
}
public static readonly DependencyProperty InputValue2Property =
DependencyProperty.Register("InputValue2", typeof(int), typeof(ValidationWindow), new PropertyMetadata(0));
public ValidationWindow()
{
InitializeComponent();
DataContext = this;
}
private void OnOk(object sender, RoutedEventArgs e)
{
string msg = Error;
if(!string.IsNullOrEmpty(Error))
{
MessageBox.Show(Error);
return;
}
DialogResult = true;
}
#region IDataErrorInfo Members
public string Error
{
get { return ((IDataErrorInfo)this)[null]; }
}
public string this[string columnName]
{
get
{
string msg = string.Empty;
if(string.IsNullOrEmpty(columnName))
{
msg += ((IDataErrorInfo)this)["InputValue"];
msg += ((IDataErrorInfo)this)["InputValue2"];
}
else
{
switch(columnName)
{
case "InputValue":
if(InputAllowed)
{
if(InputValue <= 0)
{
msg += "InputValue must be greater that 0!";
}
}
break;
case "InputValue2":
if(InputValue2 <= 0)
{
msg += "InputValue2 must be greater that 0!";
}
break;
}
}
return msg;
}
}
#endregion
}
}
We've been using this to force validation after programatcially changing text, should work as well if you call it in reponse to your checkbox's events:
var binding = someTextBox.GetBindingExpression( TextBox.TextProperty );
if( binding == null )
return;
binding.UpdateSource();
No actual difference from stijn's solution but since we're all a little lazy:
public static class DependencyPropertyExtensions
{
public static bool UpdateSource(this FrameworkElement source, DependencyProperty property)
{
var binding = source.GetBindingExpression(property);
if (binding != null)
{
binding.UpdateSource();
return true;
}
return false;
}
}

Categories

Resources