binding in wpf doesn't work, can't see why - c#

I have a wpf app I'm using some xaml code that should let me view pdf files, I just started to use data biding and don't figure way this not works for me.
here m XAML:
<Grid>
<telerik:RadPdfViewerToolBar RadPdfViewer="{Binding ElementName=pdfViewer, Mode=OneTime}" SignaturePanel="{Binding ElementName=signaturePanel, Mode=OneTime}"/>
<telerik:SignaturePanel x:Name="signaturePanel" PdfViewer="{Binding ElementName=pdfViewer, Mode=OneWay}" Grid.Row="1"/>
<telerik:RadPdfViewer x:Name="pdfViewer" DocumentSource="{Binding Path=PathOfPdf, Mode=TwoWay}" DataContext="{Binding CommandDescriptors, ElementName=pdfViewer}" telerik:RadPdfViewerAttachedComponents.RegisterSignSignatureDialog="True" telerik:RadPdfViewerAttachedComponents.RegisterFindDialog="True" Grid.Row="2" telerik:RadPdfViewerAttachedComponents.RegisterSignaturePropertiesDialog="True" telerik:RadPdfViewerAttachedComponents.RegisterContextMenu="True"/>
<Grid>
And here code behind:
public partial class Page2 : Page, INotifyPropertyChanged
{
public Page2()
{
InitializeComponent();
DataContext = this;
}
private string _pathOfPdf= #"D:\MyFile.pdf";
public string PathOfPdf
{
get{ return _pathOfPdf; }
set{
if (_pathOfPdf != value)
{
_pathOfPdf = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
If I'm not using Biding it works fine. I if I do(on XAML):
DataContext="D:\MyFile.pdf" it shows the pdf

You need to either set the DataContext of the control or the source of the binding to the Page where the source property is defined:
<telerik:RadPdfViewer x:Name="pdfViewer"
DocumentSource="{Binding Path=PathOfPdf, RelativeSource={RelativeSource AncestorType=Page}}" />

Related

creating dynamic controls with MVVM

I am trying to create a dynamic control with using mvvm for the first time. I want to generate buttons dynamically and have the content display inside of the buttons. I am sure I am missing something really easy here, but I have no idea what it could be. When I run the code, nothing appears on the interface, though I can see AvailableMonitorOC populate in the constructor...
Here is my ViewModel where I manually add buttons to an observable collection for simplicity sake of this example:
public class CreateAndDisplayViewModel {
public ObservableCollection<AvailableMonitorBo> AvailableMonitorOC = new ObservableCollection<AvailableMonitorBo>();
public CreateAndDisplayViewModel() {
availableMonitorBo = new AvailableMonitorBo();
availableMonitorBo.AvailableMonitorLabel = "Label 1";
AvailableMonitorOC.Add(availableMonitorBo);
availableMonitorBo.AvailableMonitorLabel = "Label 2";
AvailableMonitorOC.Add(availableMonitorBo);
}
private AvailableMonitorBo availableMonitorBo;
public AvailableMonitorBo AvailableMonitorBo {
get { return availableMonitorBo; }
set {
availableMonitorBo = value;
}
}
}
Here is my model:
public class AvailableMonitorBo : INotifyPropertyChanged {
private string availableMonitorLabel { get; set; }
public string AvailableMonitorLabel {
get { return availableMonitorLabel; }
set {
availableMonitorLabel = value;
OnPropertyChanged("AvailableMonitorLabel");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And here is the xaml:
<ListView Grid.Row="2" Grid.Column="1"
ItemsSource="{Binding AvailableMonitorOC, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Content="{Binding AvailableMonitorLabel}"
Width="100"
Height="25"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The main reason for you lack of display is that AvailableMonitorOC needs to be a property of CreateAndDisplayViewModel, not a field as it is currently.
You're also only creating one AvailableMonitorBo instance and changing its caption each time.

WPF ~ Trouble with Binding & INotifyPropertyChanged

WPF n00bie here, trying to get his UI to work properly.
So I made this test example. The textblock bound to HeaderText1 changes correctly at the launch of the app, but the textblock bound to HeaderText2 doesn't update after clicking the button.
What am I doing wrong? Thanks in advance!!
<Window x:Class="DataBinding.DataContextSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataContextSample" Height="142.596" Width="310">
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
<Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button>
</WrapPanel>
<TextBlock Text="{Binding Path=DataContext.HeaderText}"></TextBlock>
<TextBlock Text="{Binding Path=DataContext.HeaderText2}"></TextBlock>
</StackPanel>
</Window>
Main window class:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace DataBinding
{
public partial class DataContextSample : Window
{
public string HeaderText { set; get; }
public DataContextSample()
{
HeaderText = "YES";
InitializeComponent();
this.DataContext = this;
}
private void btnUpdateSource_Click(object sender, RoutedEventArgs e)
{
BindingExpression binding = txtWindowTitle.GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();
Source source = new Source();
source.HeaderText2 = "YES2";
}
}
}
And the INotifyPropertyChanged class
using System.ComponentModel;
namespace DataBinding
{
public class Source : INotifyPropertyChanged
{
public string HeaderText2 { set; get; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
}
First of all you are doing many things wrong.
You should not be using the window as it's own datacontext, you should have a viewmodel that you set.
You should not be using event handlers in the view to manipulate the viewmodel. You should bind the button to a command.
Your source seems to be a "viewmodel", consider renaming it to MainWindowViewModel (for clarity) and then do this.
public class MainWindowViewModel : INotifyPropertyChanged
{
private string headerText;
private string headerText2;
private ICommand updateHeaderText2;
public string HeaderText
{
set
{
return this.headerText;
}
get
{
this.headerText = value;
// Actually raise the event when property changes
this.OnPropertyChanged("HeaderText");
}
}
public string HeaderText2
{
set
{
return this.headerText2;
}
get
{
this.headerText2 = value;
// Actually raise the event when property changes
this.OnPropertyChanged("HeaderText2");
}
}
public ICommand UpdateHeaderText2
{
get
{
// Google some implementation for ICommand and add the MyCommand class to your solution.
return new MyCommand (() => this.HeaderText2 = "YES2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
And set this viewmodel to the datacontext of your window.
this.DataContext = new MainWindowViewModel();
And then in your xaml you should bind to the viewmodel as such
<Window x:Class="DataBinding.DataContextSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataContextSample" Height="142.596" Width="310">
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<!-- Not sure what this binding is? -->
<TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
<Button Name="btnUpdateSource" Command="{Binding UpdateHeaderText2}" Margin="5,0" Padding="5,0">*</Button>
</WrapPanel>
<TextBlock Text="{Binding HeaderText}"></TextBlock>
<TextBlock Text="{Binding HeaderText2}"></TextBlock>
</StackPanel>
</Window>
You set the DataContext to this (the window). You don't have a property named HeaderText2 in the DataContext so the second binding won't work.
I'd do this (without changing your code too much, in reality I'd do a proper MVVM approach):
public partial class DataContextSample : Window
{
public Source Source { get; set; }
public string HeaderText { set; get; }
public MainWindow()
{
InitializeComponent();
HeaderText = "YES";
Source = new Source { HeaderText2 = "YES" };
DataContext = this;
}
private void btnUpdateSource_Click(object sender, RoutedEventArgs e)
{
BindingExpression binding = txtWindowTitle.GetBindingExpression(TextBox.TextProperty);
if (binding != null)
{
binding.UpdateSource();
}
Source.HeaderText2 = "YES2";
}
}
I added a new property called Source which is of type Source. Set its initial HeaderText2 to the same "YES" in the constructor and in the button click change that to "YES2".
You have to change your Source class as well, to actually notify about changes:
public class Source : INotifyPropertyChanged
{
private string _headerText2;
public string HeaderText2
{
get { return _headerText2; }
set
{
_headerText2 = value;
OnPropertyChanged("HeaderText2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
And then in your XAML:
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
<Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button>
</WrapPanel>
<TextBlock Text="{Binding Path=HeaderText}"></TextBlock>
<TextBlock Text="{Binding Path=Source.HeaderText2}"></TextBlock>
</StackPanel>
Well there are a few issues with your code.
First of all, you never assign your "Source" to a datacontext, so there's no way for your second TextBlock to find the value of "HeaderText2".
If however you would assign your "Source" to the textblocks datacontext then we could fetch the value of "HeaderText2". Consider the code below
<Window x:Class="DataBinding.DataContextSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataContextSample" Height="142.596" Width="310">
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
<Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button>
</WrapPanel>
<TextBlock Text="{Binding Path=HeaderText}"></TextBlock>
<TextBlock Name="TextBlock2" Text="{Binding Path=HeaderText2}"></TextBlock>
</StackPanel>
</Window>
We have given your second Textblock a name, "TextBlock2" and also removed the "Datacontext"-part from your binding.
Then we have moved the Creation of your "Source" object from the button event to the windows constructor (there is no need to make a new one everytime we click a button when all we want to do is to update a property)
public partial class DataContextSample : Window
{
public string HeaderText { set; get; }
private Source source { get; set; }
public DataContextSample()
{
...
source = new Source();
TextBlock2.DataContext = source;
...
}
...
}
And then in your buttons click-event we assign your databound property a value of "YES2".
private void btnUpdateSource_Click(object sender, RoutedEventArgs e)
{
...
source.HeaderText2 = "YES2";
}
There is however one more detail. Your class "Source" does implement "INotifyPropertyChanged", but it never "uses" it. By that I mean, that when you assign a value to your property "HeaderText2" you never actually "notify" the UI that something has changed with it, and thus the UI will not fetch the new value. Consider the code below:
public class Source : INotifyPropertyChanged
{
public string HeaderText2 { set
{
headerText2 = value;
OnPropertyChanged("HeaderText2");
}
get
{
return headerText2;
}
}
string headerText2;
...
}
So let's take a look at what we've done with the property "HeaderText2". Everytime the "HeaderText2" gets a value assigned, it will first save the value in a privat property (so that we can read from it later). But in addition to that we also call the "OnPropertyChanged" method with our Propertys name. That method will in turn check if anyone is "listening" to our "PropertyChanged"-event (and since we have a databinding on the current object, someone is listening), and create a new event.
Now we have assigned a datasource to your textblock with a path to "HeaderText2", we are notifying all listeners when we update "HeaderText2" on the datasource and we are updating "HeaderText2" on the buttons click event.
Happy coding!

Change datatemplate of contentcontrol on change of datacontext property

I have a contentcontrol that is bound to an object (data property of DataContext in the example). Whenever the object referenced by data changes, I want to re-select the datatemplate. How can I do that?
<ContentControl Name="rootData" Content="{Binding data}"
ContentTemplateSelector="{StaticResource myTemplateSelector}"/>
In Case you are changing whole data type then your view should be as below, Only difference is removing key and using DataType for DataTemplate, this is called implicit data template
<Window x:Class="TextBindingFormatting.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TextBindingFormatting"
xmlns:viewModels="clr-namespace:TextBindingFormatting.ViewModels"
Title="MainWindow" Height="350" Width="555">
<Window.Resources>
<local:MyTemplateSelector x:Key="MyTemplateSelector"></local:MyTemplateSelector>
<DataTemplate DataType="{x:Type viewModels:Student}">
<StackPanel Orientation="Horizontal">
<Label>Id</Label>
<TextBlock Text="{Binding Id}"></TextBlock>
<Label>Name</Label>
<TextBlock Text="{Binding Name}"></TextBlock>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Parent}">
<StackPanel Orientation="Vertical">
<Label>Name</Label>
<TextBlock Text="{Binding Name}"></TextBlock>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<ContentControl Content="{Binding Data}" >
</ContentControl>
<Button Content="Change DataTemplate" Click="ButtonBase_OnClick"></Button>
</StackPanel>
</Grid>
code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel() {Data = new Student{Id = 1, Name = "Student"}};
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
var vm = this.DataContext as MainWindowViewModel;
vm.Data = new Parent() {Name = "This is parent"};
}
}
I have two classes as below
public class Student : INotifyPropertyChanged
{
private string _name;
private int _id;
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public string Name {
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public int Id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("Id");
}
}
}
And another
public class Parent : INotifyPropertyChanged
{
private string _name;
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
If you have same data type and different values for Data then you can use DataTemplate Selector as below, Otherwise just use DataType attribute of DataTemplate and you don't even need datatemplate selector.
Below is sample code to select template every time you change the data.
MainWindow.xaml
<Window x:Class="TextBindingFormatting.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TextBindingFormatting"
Title="MainWindow" Height="350" Width="555">
<Window.Resources>
<local:MyTemplateSelector x:Key="MyTemplateSelector"></local:MyTemplateSelector>
<DataTemplate x:Key="Template1">
<StackPanel Orientation="Horizontal">
<Label>Label 1</Label>
<Label>Label 2</Label>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="Template2">
<StackPanel Orientation="Vertical">
<Label>Label 1</Label>
<Label>Label 2</Label>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<ContentControl Content="{Binding Data}" ContentTemplateSelector="{StaticResource MyTemplateSelector}"></ContentControl>
<Button Content="Change DataTemplate" Click="ButtonBase_OnClick"></Button>
</StackPanel>
</Grid>
Below is the Code Behind, Ideally button click should be handled using command, but for quick example I have implemented in code behind just to trigger change of data.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel() {Data = "1"};
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
var vm = this.DataContext as MainWindowViewModel;
vm.Data = "2";
}
}
Below is the ViewModel for MainWindow
namespace TextBindingFormatting.ViewModels
{
public class MainWindowViewModel : INotifyPropertyChanged
{
private string _data;
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public string Data
{
get { return _data; }
set
{
_data = value;
OnPropertyChanged("Data");
}
}
}
}
DataTemplate Selector
namespace TextBindingFormatting
{
public class MyTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var element = container as FrameworkElement;
if (element == null || item == null)
return base.SelectTemplate(item, container);
if (item.ToString() == "1")
return element.FindResource("Template1") as DataTemplate;
if (item.ToString() == "2")
return element.FindResource("Template2") as DataTemplate;
return base.SelectTemplate(item, container);
}
}
}
Found a possible solution here from Rachel:
Change Data template dynamically
DataTemplateSelector wont trigger on PropertyChange but setting triggers does the job for me!

WPF data binding with inner elements

I want to bind my StackPanel component to an object and its properties to elements it the StackPanel
<StackPanel Grid.Column="0" Grid.Row="0" Name="device1" Background="#CC119EDA" DataContext="{Binding}">
<Label FontSize="22" Foreground="White">Desk #1</Label>
<TextBox Text="{Binding Name}" />
</StackPanel>
In code behind
device1.DataContext = new Class { Name = "Name" };
What is wrong with this binding? Thanks
You could try like this. When u set the property to the Object , the UI Thread is not aware of the change , so you need to Implement RaisePropertyChange Mechanism. Using MvvmLight Toolkit is great advantage . Here the window datacontext is set to so could inherit all elements.
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string _name;
public string MyName
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("MyName");
}
}
public MainWindow()
{
InitializeComponent();
MyName = "Eldho";
this.DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Xaml
<StackPanel>
<Label>Hi,</Label>
<TextBox Text="{Binding MyName}"/>
</StackPanel>

How to correct bind property to textbox?

I write these code in mainpage.xaml
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<StackPanel>
<TextBox x:Name="xxx" Text="{Binding Test}" TextChanged="xxx_TextChanged" />
<Button x:Name="click" Click="click_Click" Content="click" />
</StackPanel>
</Grid>
And these in mainpage.xaml.cs
private string test;
public string Test
{
get { return test; }
set
{
if (test != value)
{
test = value;
OnPropertyChanged("Test");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
// Constructor
public MainPage()
{
InitializeComponent();
}
private void xxx_TextChanged(object sender, TextChangedEventArgs e)
{
Debug.WriteLine(Test);
Debug.WriteLine(test);
}
But Test isn't binded to textbox, when I write smth to textbox Test isn't changed.
What I doing wrong and how to correct that ?
Try setting BindingMode to TwoWay:
Text="{Binding Test, Mode=TwoWay}"
The other thing I've noticed, is that your binding to work need DataContext to be set, but you don't do that in your example. One way to do this would be something like this:
public MainPage()
{
InitializeComponent();
ContentPanel.DataContext = this;
}
If staying in Xaml is preferred, you can use RelativeSource property to bind to your page in Xaml, without setting DataContext:
Text="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, //or Page
Path=Test, Mode=TwoWay}"
Another thing, Test will be set not after every character you type in your TextBox, but after user will finish editing text, for example by switching active control to next one.

Categories