WPF: DataBinding a PointCollection for a custom UIElement - c#

I'm trying to make a WPF UIElement that does some special rendering.
The problem is that I can't get databinding to work for a PointCollection. Inside OnRender() of the GraphElement, the Points property is null, which doesn't make any sense to me.
class GraphElement : UIElement
{
public static readonly DependencyProperty PointsProperty = DependencyProperty.Register(
"Points",
typeof(PointCollection),
typeof(GraphElement),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
public PointCollection Points
{
get { return (PointCollection)GetValue(PointsProperty); }
set { SetValue(PointsProperty, value); }
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
//Points is null
Debug.Assert(Points != null);
}
}
class TestViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private PointCollection _viewModelPoints;
public PointCollection ViewModelPoints
{
get { return _viewModelPoints; }
set
{
_viewModelPoints = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("ViewModelPoints"));
}
}
}
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestApp"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Canvas>
<local:GraphElement Points="{Binding Path=ViewModelPoints}"/>
</Canvas>
</Grid>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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.Navigation;
using System.Windows.Shapes;
namespace TestApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
var testViewModel = new TestViewModel();
testViewModel.ViewModelPoints = new PointCollection();
testViewModel.ViewModelPoints.Add(new Point(0.0, 0.0));
DataContext = testViewModel;
InitializeComponent();
}
}
}

Change the inheritance on GraphElement from UIElement to FrameworkElement (which in turn inherits UIElement). This is where you have the DataContext dp etc.
class GraphElement : FrameworkElement
{
//...
}
Copied your code into a sample project (changing UIElement to FrameworkElement) and Points is not null in OnRender. Uploaded the project here.

Related

How do I bind an event captured by a XAML element to a method in an arbitrary class in C#?

I am a C# newbie trying to build a simple MVVM app, and I am having trouble tying events in my XAML View to methods in my Model or ViewModel. I understand why MVVM is used and feel like I get the broad strokes of how to put an MVVM app together, but I am lost in the details. I apologize in advance if it looks like I have no idea what I'm doing, but I don't, despite lots of reading up on the subject.
I want btnUpdate_Click in MainScreenViewModel to execute when the button is clicked, but I get the error
MC6005 Click="vm:btnUpdate_Click" is not valid. 'vm:btnUpdate_Click' is not a valid event handler method name. Only instance methods on the generated or code-behind class are valid.
If my classes are public and in the same namespace, what do I need to do to make them visible from my View? I don't want to move methods back to the MainWindow class.
<Window x:Class="SFM_Calculator.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:SFM_Calculator"
xmlns:vm="MainScreenViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
>
<Window.DataContext>
<local:SFMModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
...
<ColumnDefinition/>
</Grid.ColumnDefinitions>
...
</Grid.RowDefinitions>
...
<Button
Grid.Column="1"
Grid.Row="2"
x:Name="btnUpdate"
Content="Update"
Click="vm:btnUpdate_Click"
/>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Linq;
using System.ComponentModel;
namespace SFM_Calculator
{
public class MainScreenViewModel : INotifyPropertyChanged
{
private void btnUpdate_Click(object sender, System.Windows.RoutedEventArgs e)
{
TestInt = 999;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
public SFMModel sfmModel = new SFMModel();
private int _testInt;
public int TestInt
{
get { return _testInt; }
set { _testInt = value; }
}
public MainScreenViewModel()
{
Debug.WriteLine("Got here.");
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace SFM_Calculator
{
public class SFMModel : INotifyPropertyChanged
{
private int _tprop;
public int TProp
{
get { return _tprop; }
set { _tprop = value; }
}
public SFMModel ()
{
TProp = 69;
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
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.Navigation;
using System.Windows.Shapes;
using System.Configuration;
namespace SFM_Calculator
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
Your viewModel should expose a command, not a method. Plus you should access the exposed command via binding mechanism.
Sample Command that implements required ICommand interface
internal class Command : ICommand
{
private readonly Action execute;
public Command(Action execute)
{
this.execute = execute;
}
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter)
{
return true;
}
public void Execute(object? parameter)
{
execute();
}
}
Sample ViewModel that exposes ICommand (not a regular method as in your example). It will change the value of Text property after clicking the button - just to show that it works.
internal class ViewModel : INotifyPropertyChanged
{
public string Text { get; private set; }
public ICommand AwesomeCommand { get; }
public ViewModel()
{
AwesomeCommand = new Command(() => {
Text = "Button clicked";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
});
}
public event PropertyChangedEventHandler? PropertyChanged;
}
MainWindow:
<StackPanel>
<Button Command="{Binding AwesomeCommand}"></Button>
<Label Content="{Binding Text}" Height="100"></Label>
</StackPanel>
code-behind MainWindow, to hook-up ViewModel and the view (MainWindow):
public MainWindow()
{
DataContext = new ViewModel();
InitializeComponent();
}

Datagrid stays Empty but ObservableCollection has values

Currently i am trying to learn WPF, but i have hit a brickwall with my current Problem, after many hours googling and trying to fix it on my own. I am trying to display the Model Province. I have found multiple similar Problems but i couldn't figure it out on my own. After having checked the Output there was no mention of any error. Currently the Window shows just the empty Model but no data even though the Observable Collection gets updated. So before i completely destroy my interest in WPF i am asking for help.
MyView
<Window x:Class="isnuaatest.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:isnuaatest"
xmlns:local1="clr-namespace:isnuaatest.Models"
xmlns:local2="clr-namespace:isnuaatest.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local2:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid>
<DataGrid ItemsSource="{Binding Provinces, UpdateSourceTrigger=PropertyChanged}">
</DataGrid>
</Grid>
<StackPanel Width="200" Margin="50">
<Button x:Name="OpenSaveFile" Click="OpenSaveFile_Click">OpenSaveFile</Button>
</StackPanel>
</Grid>
My View Model
using isnuaatest.Helper;
using isnuaatest.Models;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Input;
namespace isnuaatest.ViewModel
{
public class MainWindowViewModel : INotifyPropertyChanged
{
public ObservableCollection<Province> _province;
public ObservableCollection<Province> Provinces
{
get { return this._province; }
set
{
_province = value;
}
}
public MainWindowViewModel() : base()
{
this.Provinces = new ObservableCollection<Province>();
}
private string _savegamePath;
public string SavegamePath
{
get { return _savegamePath; }
set { _savegamePath = value; OnPropertyChanged("SavegamePath"); GetProvinces(_savegamePath);}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var savegamefile = this.PropertyChanged;
if (savegamefile != null)
savegamefile(this, new PropertyChangedEventArgs(propertyName));
}
public event EventHandler OnItemChanged;
public void GetProvinces(string path)
{
Reader reader = new Reader();
if (_savegamePath != null)
{
FileStream fs = File.OpenRead(path);
List<Province> listofProvinces = reader.ReadTextString(fs);
foreach (Province province in listofProvinces)
{
Provinces.Add(new Province()
{
Aristocrats = province.Aristocrats,
Artisans = province.Artisans
});
}
}
}
}
}
Code Behind
using isnuaatest.Helper;
using isnuaatest.Models;
using isnuaatest.ViewModel;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
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.Navigation;
using System.Windows.Shapes;
namespace isnuaatest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindowViewModel _vm = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
private void OpenSaveFile_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog fileDialog = new OpenFileDialog();
fileDialog.Multiselect = false;
dynamic result = fileDialog.ShowDialog();
if (result == true)
{
_vm.SavegamePath = fileDialog.FileName;
}
}
}
}
My thinking is that maybe the Data Context wont update, because the data is in the Observable Collection. If this is true how can i update the Data Context, i already tried adding it in xaml to no avail.
Thanks
You actually create 3 different MainWindowViewModel objects - one in xaml and two in code behind. You can get rid of one in xaml, once in MainWindow constructor you set DataContext xaml-one is overridden.
But two objects in code-behind cause your problem - you load file into _vm object, but it's not the one that is held in DataContext.
To fix your problem use _vm for DataContext and not the new object:
public MainWindowViewModel _vm = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _vm;
}
Change your Provinces:
public ObservableCollection<Province> Provinces
{
get { return this._province; }
set
{
_province = value;
OnPropertyChanged("Provinces");
}
}

Data Grid control not showing ObservableCollection data

I'm rather new to WPF and C# in general. I'm playing around with it and encountered a problem which I feel like would be a piece of cake for an expert but I have no idea what I'm doing wrong.
I'm trying to create a simple DataGrid control (within a TabControl) and bind it to an ObservableCollection object.
I use microsoft's Data Binding Demo provided in their data binding overview as a basis for my code.
MainWindow XAML:
<Window x:Class="PetProject.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:PetProject"
mc:Ignorable="d"
Title="PetProject" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=Dogs}"
x:Key="DogsDataView" />
</Window.Resources>
<Grid Margin="8,8,8,8">
<TabControl>
<TabItem Header="Dogs">
<DataGrid ItemsSource="{Binding Source={StaticResource DogsDataView}}">
</DataGrid>
</TabItem>
</TabControl>
</Grid>
</Window>
Code-Behind:
using System;
using System.Collections.Generic;
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.Navigation;
using System.Windows.Shapes;
namespace PetProject
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
///
public partial class MainWindow : Window
{
CollectionViewSource DogsDataView;
public MainWindow()
{
InitializeComponent();
DogsDataView = (CollectionViewSource)(this.Resources["DogsDataView"]);
}
}
}
The App XAML is
<Application x:Class="PetProject.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PetProject"
Startup="AppStartup">
<!--StartupUri="MainWindow.xaml"-->
</Application>
code-behind:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace PetProject
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private ObservableCollection<Dog> dogs = new ObservableCollection<Dog>();
void AppStartup(object sender, StartupEventArgs args)
{
LoadData();
MainWindow mainWindow = new MainWindow();
mainWindow.Show();
}
public ObservableCollection<Dog> Dogs
{
get { return this.dogs; }
set { this.dogs = value; }
}
private void LoadData() {
Dog Johnny = new Dog("Johnny",1325);
Dog Diamond = new Dog("Diamond",1327);
this.Dogs.Add(Johnny);
this.Dogs.Add(Diamond);
}
}
}
Dog is just a class implementing the INotifyPropertyChanged interface (which for now does not do anything):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace PetProject
{
public class Dog : INotifyPropertyChanged
{
private string name;
private string number;
public event PropertyChangedEventHandler PropertyChanged;
public Dog(string name, int number)
{
this.name = name;
this.number = number.ToString("D4");
}
protected void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
I'd appreciate any help in understanding why the DataGrid is not populated.
Also, any suggestion on bad coding habits or improvement to the code would be very welcome, as I'm in a very initial learning-by-experience phase.
Thanks!
You can't bind to private fields. You can only bind to public properties. As far as the DataGrid is concerned, Dog has no information to display.
public class Dog : INotifyPropertyChanged
{
private string _name;
private string _number;
public Dog(string name, int number)
{
Name = name;
Number = number.ToString("D4");
}
public String Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
NotifyPropertyChanged(nameof(Name));
}
}
}
public String Number
{
get { return _number; }
set
{
if (value != _number)
{
_number = value;
NotifyPropertyChanged(nameof(Number));
}
}
}
I'm prefixing your private fields with underscores because that's standard practice. It's standard practice because having two identifiers which differ only by case is a recipe for confusion and bugs.
First of all, I suggest you to read about MVVM principles then maybe choose a MVVM framework to use with WPF. For instance MVVM light toolkit is a good choice to start and understand MVVM.
For your example, here are just a few remarks about your code:
I suggest you to group all your 'business' data into a viewModel class (see MVVM practices all around the web) - nothing in the App class...
This ViewModel will implement 'INotifyPropertyChanged' interface
So the Dogs property will be located into this ViewModel and will raise the 'PropertyChanged' event in its setter (what is not currently the case in your sample)
There are several MVVM frameworks that would 'bind' automatically your views to your view model, but to understand, the main goal is to set your Window.DataContext with the appropriate ViewModel.
That's why you can restore in App.xaml: StartupUri="MainWindow.xaml"
Then to load your ViewModel, you can do something like that to load your Dogs collection:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// For test: LOAD & SET your DataContext here
//
var myDogViewmodel = new DogViewModel();
myDogViewModel.LoadData();
this.DataContext = myDogViewmodel;
}
}
Your ViewModel should look like something like that:
public class DogViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Dog> _dogs;
public ObservableCollection<Dog> Dogs
{
get { return _dogs; }
set
{
_dogs = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Dogs"));
}
}
public void LoadData()
{
// ....
}
}
Then your Dog class must also implement INotifuPropertyChanged interface:
public class Dog : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
private int _number;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
}
}
}
public int Number
{
get => _number;
set
{
if (_number != value)
{
_number = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Number"));
}
}
}
}
Finally,in your MainWindow.xaml:
>
<Window x:Class="PetProject.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:PetProject"
mc:Ignorable="d"
Title="PetProject" Height="350" Width="525">
<Grid Margin="8,8,8,8">
<TabControl>
<TabItem Header="Dogs">
<DataGrid ItemsSource="{Binding Dogs}" />
</TabItem>
</TabControl>
</Grid>
It should work now ;) Tell me if it's clear. Get familiar with MVVM...

Binding on Dependency Property of UserControl

I'm trying to create UserControls to be able to reuse them later in the WPF application. I choose to build a small project to train myself, but I can't make it work.
The objective is to have a TextBox whose content will be sent as a Label Text with the click on a Button.
I've read and tried the solutions on those links :
XAML Binding on Dependency Property
Custom Dependency Properties
Simple Dependency Property and UserControl issues in C#
Add dependency property to control
But even the starting text I set in the constructor doesn't show up, and the button click does nothing at all.
Here are my files :
MyControl.xaml
<UserControl x:Class="WpfApplication1.MyControl"
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:WpfApplication1"
mc:Ignorable="d"
Name="control"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Text="{Binding TextBoxContent,ElementName=control}"/>
<Button Content="Print Entry" Grid.Row="1" Command="{Binding ButtonCommmand,ElementName=control}"/>
<Label Grid.Row="2" Content="{Binding LabelContent,ElementName=control}"/>
</Grid>
</UserControl>
MyControl.xaml.cs
using GalaSoft.MvvmLight.CommandWpf;
using System;
using System.Collections.Generic;
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.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1
{
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
ButtonCommmand = new RelayCommand(Action);
}
public string TextBoxContent
{
get
{
return (string)GetValue(TextBoxContentProperty);
}
set
{
SetValue(TextBoxContentProperty, value);
}
}
public RelayCommand ButtonCommmand
{
get
{
return (RelayCommand)GetValue(ButtonCommandProperty);
}
set
{
SetValue(ButtonCommandProperty, value);
}
}
public string LabelContent {
get
{
return (string)GetValue(LabelContentProperty);
}
set
{
SetValue(LabelContentProperty, value);
}
}
public void Action()
{
LabelContent = TextBoxContent;
}
public static readonly DependencyProperty TextBoxContentProperty = DependencyProperty.Register("TextBoxContent", typeof(string), typeof(MyControl), new PropertyMetadata(""));
public static readonly DependencyProperty ButtonCommandProperty = DependencyProperty.Register("ButtonCommmand", typeof(RelayCommand), typeof(MyControl), new PropertyMetadata(null));
public static readonly DependencyProperty LabelContentProperty = DependencyProperty.Register("LabelContent", typeof(string), typeof(MyControl), new PropertyMetadata(""));
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.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:WpfApplication1"
xmlns:control="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<control:MyControl DataContext="{Binding customControl}" TextBoxContent="{Binding Text,Mode=TwoWay}" LabelContent="{Binding EndText,Mode=TwoWay}" ButtonCommmand="{Binding Command,Mode=TwoWay}"/>
</Grid>
</Window>
MainWindow.xaml.cs
using GalaSoft.MvvmLight.CommandWpf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
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.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
MyControl customControl = new MyControl();
public MainWindow()
{
InitializeComponent();
Command = new RelayCommand(Action);
Text = "Testing... Testing... 1, 2, 3,...";
}
private string text;
public string Text
{
get
{
return text;
}
set
{
text = value;
NotifyPropertyChanged();
}
}
private string endText;
public string EndText
{
get
{
return endText;
}
set
{
endText = value;
NotifyPropertyChanged();
}
}
private RelayCommand command;
public RelayCommand Command
{
get
{
return command;
}
set
{
command = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void Action()
{
EndText = Text;
}
private void NotifyPropertyChanged([CallerMemberName]string PropertyName = "")
{
if (!String.IsNullOrEmpty(PropertyName))
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
}
Thank you a lot for your help !
You are creating another instance of your UserControl in the code-behind of the window:
MyControl customControl = new MyControl();
What you want to do is to bind the properties of the UserControl that you have defined in your XAML to the properties of the window. For this to work you should set the DataContext of the window to itself:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
Command = new RelayCommand(Action);
Text = "Testing... Testing... 1, 2, 3,...";
}
}
...and remove this from your XAML:
DataContext="{Binding customControl}"

WPF - Unable to set properties of dynamically added User Control via deserialization

File des.txt --> this file is deserialized to xaml tree
<NewLabel NewText="some new text" LabelText="yes" xmlns="clr-namespace:WpfPractice;assembly=WpfPractice" xmlns:av="http://schemas.microsoft.com/winfx/2006/xaml/presentation"><av:Grid><av:Canvas><av:TextBox Width="100" Height="30">yes</av:TextBox></av:Canvas></av:Grid></NewLabel>
Mainwindow.xaml
<Window x:Class="WpfPractice.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfPractice"
Title="MainWindow"
Width="525"
Height="350">
<Grid Name="theGrid">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button Grid.Row="1"
Width="50"
Height="25"
Click="Button_Click_2"
Content="Click" />
</Grid>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.IO;
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.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml;
namespace WpfPractice
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
NewLabel someNewLabel;
public MainWindow()
{
InitializeComponent();
someNewLabel = (NewLabel)DeserializeXaml(File.ReadAllText(#"D:\des.txt"));
theGrid.Children.Add(someNewLabel);
}
void Serialize()
{
NewLabel theNewLabel = new NewLabel();
theNewLabel.NewText = "some new text";
string xamlString = XamlWriter.Save(theNewLabel);
XmlReader reader = XmlReader.Create(new StringReader(xamlString));
UIElement copy = XamlReader.Load(reader) as UIElement;
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
someNewLabel.LabelText = "new value";
someNewLabel.SetLabelValue("asdfsadgsafgreg");
//Serialize();
}
public object DeserializeXaml(string xaml)
{
StringReader stringReader = new StringReader(xaml);
XmlReader xmlReader = XmlReader.Create(stringReader);
return System.Windows.Markup.XamlReader.Load(xmlReader);
}
}
}
NewLabel.xaml
<UserControl x:Class="WpfPractice.NewLabel"
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"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid Name="theGrid">
</Grid>
NewLabel.xaml.cs
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.Navigation;
using System.Windows.Shapes;
namespace WpfPractice
{
/// <summary>
/// Interaction logic for NewLabel.xaml
/// </summary>
public partial class NewLabel : UserControl, INotifyPropertyChanged
{
public static DependencyProperty NameProperty =
DependencyProperty.Register("NewText",
typeof(string),
typeof(NewLabel),
new PropertyMetadata(String.Empty));
public string NewText
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
private string _labelText = "yes";
public string LabelText
{
get
{
return _labelText;
}
set
{
_labelText = value;
OnPropertyChanged("LabelText");
}
}
Label theLabel;
public NewLabel()
{
InitializeComponent();
theLabel = new Label();
theLabel.Height = 30;
theLabel.Width = 100;
theLabel.Content = "old value";
Canvas theCanvas = new Canvas();
theCanvas.Children.Add(theLabel);
theGrid.Children.Add(theCanvas);
Binding b = new Binding("LabelText");
b.Source = this;
theLabel.SetBinding(TextBox.TextProperty, b);
theLabel.MouseDoubleClick += theLabel_MouseDoubleClick;
this.DataContext = this;
}
void theLabel_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("clicked!");
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
public void SetLabelValue(string val)
{
theLabel.Content = val;
}
}
}
Question:
Both these statements in MainWindow.xaml.cs
someNewLabel.LabelText = "new value";
someNewLabel.SetLabelValue("asdfsadgsafgreg");
do not work. In other words, I am unable to set the textbox property to "new value"
You are creating the binding in NewLable constructor, but then you load a new visual tree from a file which overrides the original one. That why you cant update the label nor by binding neither by setting the value directly because its not in the visual tree anymore.

Categories

Resources