UWP canvas dynamic content - c#

I want to be able to dynamically add different (self-created) "widgets" to my canvas and position them using Canvas.Top and Canvas.Left.
I have been able to add items using Canvas.Children.Add(), but I can't figure out how to create a binding to the Top and Left values.
Would it be a better idea to somehow bind the contents of the Canvas to a list and create all the bindings in XAML? Then again, how would I do that?

If you don't know the bindings in UWP, you can see this document.
Depending on your situation, you can consider using MVVM for binding, creating a ViewModel in Code-Behind and using Binding in the xaml to bind related properties.
CanvasPageViewModel.cs
public class CanvasPageViewModel : INotifyPropertyChanged
{
private double _rectTop;
public double RectTop
{
get => _rectTop;
set
{
_rectTop = value;
OnPropertyChanged();
}
}
private double _rectLeft;
public double RectLeft
{
get => _rectLeft;
set
{
_rectLeft = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
CanvasPage.xaml.cs
public CanvasPageViewModel viewModel = new CanvasPageViewModel();
public CanvasPage()
{
this.InitializeComponent();
this.DataContext = viewModel;
viewModel.RectTop = 20;
viewModel.RectLeft = 100;
}
CanvasPage.xaml
<Grid>
<Canvas Width="500" Height="500" Background="White">
<Rectangle Width="50" Height="50" Fill="Blue"
Canvas.Top="{Binding RectTop}"
Canvas.Left="{Binding RectLeft}"/>
</Canvas>
</Grid>
This is a simple example. If you want to modify the position of the control later, you can directly modify the data source and the UI will synchronize.
Update
If you need to manually add child elements of Canvas, you can use this method:
var myRect = new Rectangle()
{
Height = 50,
Width = 100,
Fill = new SolidColorBrush(Colors.Blue)
};
myCanvas.Children.Add(myRect);
But if you want to bind the created Rectangle element, as you said, bind the Canvas.Left property, you can write:
public CanvasPageViewModel viewModel = new CanvasPageViewModel();
public CanvasPage()
{
this.InitializeComponent();
var myRect = new Rectangle()
{
Height = 50,
Width = 100,
Fill = new SolidColorBrush(Colors.Blue)
};
Binding leftBinding = new Binding()
{
Path = new PropertyPath("RectLeft"),
Mode=BindingMode.OneWay
};
myRect.SetBinding(Canvas.LeftProperty, leftBinding);
myCanvas.Children.Add(myRect);
}
Best regards.

Related

WPF Code-Behind data binding with AutoGeneratedColumns not working

I am trying to achieve rather something simple, and I believe that my approach might be wrong. I am creating a datagrid, where the first column has a seperate width from the other ones. I am using AutoGenerateColumns=true, as it simplifies my work. I cannot use pure XAML as I do not know the amount of columns before runtime, and I was not able to connect XAML and AutoGenerateColumns, so it would use the first column's layout, and then generate the rest.
My approaches:
1) Create two data grids next to each other - the issue with that approach is the need to manage 2 seperate datagrids, I saw issues with scrolling and adjusting their sizes, so I decided to change my approach, to keep everyhting within one DataGrid as the data relates to each other.
2) Trying to get the Datagrid object from Code-Behind so I can set the Width property from the ViewModel class, this would break the MVVM model, and also was difficult for my to implement
3) Current approach - using the AutoGeneratingColumn event, I capture the first column and try to bind to its WidthProperties. Unfortunately this does not seem to work, and I do not know why.
This is my Code-Behind file for the XAML containing the DataGrid
private void DG1_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
string headername = e.Column.Header.ToString();
//Cancel the column you don't want to generate
if (headername == "DATE")
{
Binding binding = new Binding("DateColumnWidth");
binding.Source = DataGrid.DataContext; // DataGrid is the name of the DataGrid
binding.Mode = BindingMode.TwoWay;
binding.Path = new PropertyPath("DateColumnWidth");
BindingOperations.SetBinding(e.Column, ColumnDefinition.MinWidthProperty, binding);
BindingOperations.SetBinding(e.Column, ColumnDefinition.MaxWidthProperty, binding);
e.Column.Header = "Test";
}
}
This is my Proprty in the ViewModel. Whilst debugging the binding source, it attaches to the right class and I see all my properties. It also changes the header of the right column.
private int _DateColumnWidth;
public int DateColumnWidth
{
get { return _DateColumnWidth; }
set
{
_DateColumnWidth = value;
RaisePropertyChanged("DateColumnWidth");
}
}
I set the debugger to show me all the data binding tracing information, no problems arise, but the width is not updating.
What am I doing wrong?
I created a mock up based on your code and it worked. Then I looked more closely and realised you have this:
BindingOperations.SetBinding(e.Column, ColumnDefinition.MinWidthProperty, binding);
ColumnDefinition is the wrong object. It should be DataGridColumn:
BindingOperations.SetBinding(e.Column, DataGridColumn.MaxWidthProperty, binding);
Here is my test. The grid's first column is bound to a ColumnWidth property on ViewModel. There is a Slider control below the grid with the same binding. Sliding the slider changes the first column's width.
MainWindow.xaml:
<Window x:Class="SO_59604847_DataGridBoundColumnWidth.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SO_59604847_DataGridBoundColumnWidth"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid
AutoGenerateColumns="True"
ItemsSource="{Binding GridItems}"
AutoGeneratingColumn="DataGrid_AutoGeneratingColumn">
</DataGrid>
<Slider Grid.Row="1" Minimum="1" Maximum="1000" Value="{Binding ColumnWidth}" />
</Grid>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if (e.Column.Header.ToString() == "ColumnOne")
{
var binding = new Binding("ColumnWidth");
binding.Source = this.DataContext;
BindingOperations.SetBinding(e.Column, DataGridColumn.MinWidthProperty, binding);
BindingOperations.SetBinding(e.Column, DataGridColumn.MaxWidthProperty, binding);
}
}
}
ViewModel.cs:
public class ViewModel : INotifyPropertyChanged
{
private double _columnWidth;
public double ColumnWidth
{
get { return _columnWidth; }
set { _columnWidth = value; FirePropertyChanged(); }
}
private List<GridItem> _gridItems = new List<GridItem>()
{
new GridItem() { ColumnOne = "1.1", ColumnTwo = "1.2", ColumnThree = "1.3" },
new GridItem() { ColumnOne = "2.1", ColumnTwo = "2.2", ColumnThree = "2.3" },
new GridItem() { ColumnOne = "3.1", ColumnTwo = "3.2", ColumnThree = "3.3" }
};
public List<GridItem> GridItems
{
get { return _gridItems; }
}
private void FirePropertyChanged([CallerMemberName] string caller = "")
{
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class GridItem
{
public string ColumnOne { get; set; }
public string ColumnTwo { get; set; }
public string ColumnThree { get; set; }
}
It does not do two-way binding because that doesn't make sense if you're binding to the Min and Max column widths (the user can't change these - and indeed cannot change the width because the Min and Max widths are set to the same value). If your intention was to bind the width two-ways then you will need to bind to the WidthProperty dependency property and not the Min/MaxWidthProperty DPs (though, you may need a value converter then because Width is a GridLength not a plain number. Say so if that is what you were trying to do and I'll see if I can update the answer accordingly.

Generate custom item control from Dependency Property as Item Source in UserControl

I'm making a UserControl to generate a list of attached files from Dependency Property as ItemSource. But the ItemSource (DependencyProperty) count is 0.
I tried debugging and realized that the ObservableCollection in ViewModel was bound after the Constructor of my UserControl is initialized.
I'm coding in MVVM pattern, I made a function to prepare some sample data for ObservableCollection in ViewModel and inside the MainWindow I bound the DataContext of my UserControl with that ViewModel then set the ItemSource for ObservableCollection
My ViewModel code-behind:
//The properties
ObservableCollection<FileAttachmentModel> filesAttachment;
public ObservableCollection<FileAttachmentModel> FilesAttachment
{
get { return filesAttachment; }
set { filesAttachment = value; OnPropertyChanged("FilesAttachment"); }
}
//The function prepare sample data
private ObservableCollection<FileAttachmentModel> PrepareData()
{
FilesAttachment.Add(new FileAttachmentModel() { FileName = "TrackA", FilePath = "D:\trackA.png" });
FilesAttachment.Add(new FileAttachmentModel() { FileName = "TrackB", FilePath = "D:\trackB.png" });
FilesAttachment.Add(new FileAttachmentModel() { FileName = "TrackC", FilePath = "D:\trackC.png" });
}
My UserControl xaml:
<UserControl x:Class="MailSender.Controls.FileAttachment.FileAttachment"
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:MailSender.Controls.FileAttachment"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Name="fileAttachmentUC"
>
<Grid>
<WrapPanel DataContext="{Binding ElementName=fileAttachmentUC,Path=DataContext,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" x:Name="wrapPanel">
</WrapPanel>
</Grid>
</UserControl>
My UserControl code-behind:
//the property ItemSource
public static readonly DependencyProperty ItemSourceProperty = DependencyProperty.Register("ItemSource", typeof(ObservableCollection<FileAttachmentModel>), typeof(FileAttachment),new UIPropertyMetadata());
//the wrapper property
public ObservableCollection<FileAttachmentModel> ItemSource
{
get { return (ObservableCollection<FileAttachmentModel>)GetValue(ItemSourceProperty); }
set { SetValue(ItemSourceProperty, value);
}
}
//the function to generate each file attachment and add them to the Wrappanel in UserControl
//I call this function inside constructor of UserControl and pass ItemSource as parameter
void GenerateFileItem(ObservableCollection<FileAttachmentModel> lstFileAttachment)
{
if (lstFileAttachment != null && lstFileAttachment.Count>0)
{
foreach (var item in lstFileAttachment)
{
StackPanel sp = new StackPanel() { Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center };
TextBlock tbFileName = new TextBlock() { Text = item.FileName };
Button btFilePath = new Button() { Content = "X", Tag = item.FilePath };
btFilePath.Click += BtFilePath_Click;
sp.Children.Add(tbFileName);
sp.Children.Add(btFilePath);
sp.Style = Application.Current.FindResource("stackFileItem") as Style;
wrapPanel.Children.Add(sp);
}
}
}
In Usage:
<control:FileAttachment DataContext="{StaticResource vmMainWindow}" ItemSource="{Binding FilesAttachment,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
What I expect is to make a container for attached files like Outlook of Microsoft. Please help!
Thanks in advance!
You should call GenerateFileItem whenever the dependency property is set using a PropertyChangedCallback:
public static readonly DependencyProperty ItemSourceProperty = DependencyProperty.Register("ItemSource",
typeof(ObservableCollection<FileAttachmentModel>), typeof(FileAttachment), new PropertyMetadata(new PropertyChangedCallback(OnChanged));
//the wrapper property
public ObservableCollection<FileAttachmentModel> ItemSource
{
get { return (ObservableCollection<FileAttachmentModel>)GetValue(ItemSourceProperty); }
set { SetValue(ItemSourceProperty, value); }
}
private static void OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FileAttachment fa = (FileAttachment)d;
fa.GenerateFileItem(fa.ItemSource);
}
The ItemSource property cannot be set before the UserControl has been initialized.
What I was facing is the ObservableCollection I generate in ViewModel is initialized after the UserControl is initialized. The #mm8 solution has fixed my problem by waiting for the ObservableCollection in ViewModel is initialized first and then pass it by the property I bound in MainWindow. Then the UserControl will be initialized and get the ObservableCollection that is passed from ViewModel then generate the custom controls inside my "wrapPanel".

Set DataContext for multiple controls with XAML

I have different groups of controls bound to different categories of ViewModel classes.
The ViewModels are
MainViewModel
VideoViewModel
AudioViewModel
Question
How can I set the DataContext with XAML instead of C#?
1. I tried adding DataContext="{Binding VideoViewModel}" to the ComboBox XAML, but it didn't work and the items came up empty.
2. I also tried grouping all the ComboBoxes of a certain category inside a UserControl with the DataContext:
<UserControl DataContext="{Binding VideoViewModel}">
<!-- ComboBoxes in here -->
</UserControl>
3. Also tried setting the <Window> DataContext to itself DataContext="{Binding RelativeSource={RelativeSource Self}}"
Data Context
I'm currently setting the DataContext this way for the different categories of controls:
public MainWindow()
{
InitializeComponent();
// Main
this.DataContext =
tbxInput.DataContext =
tbxOutput.DataContext =
cboPreset.DataContext =
MainViewModel.vm;
// Video
cboVideo_Codec.DataContext =
cboVideo_Quality.DataContext =
tbxVideo_BitRate.DataContext =
cboVideo_Scale.DataContext =
VideoViewModel.vm;
// Audio
cboAudio_Codec.DataContext =
cboAudio_Quality.DataContext =
tbxAudio_BitRate.DataContext =
tbxAudio_Volume.DataContext =
AudioViewModel.vm;
}
XAML ComboBox
<ComboBox x:Name="cboVideo_Quality"
DataContext="{Binding VideoViewModel}"
ItemsSource="{Binding Video_Quality_Items}"
SelectedItem="{Binding Video_Quality_SelectedItem}"
IsEnabled="{Binding Video_Quality_IsEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="105"
Height="22"
Margin="0,0,0,0"/>
Video ViewModel Class
public class VideoViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void OnPropertyChanged(string prop)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(prop));
}
}
public VideoViewModel() { }
public static VideoViewModel _vm = new VideoViewModel();
public static VideoViewModel vm
{
get { return _vm; }
set
{
_vm = value;
}
}
// Items Source
private List<string> _Video_Quality_Items = new List<string>()
{
"High",
"Medium",
"Low",
};
public List<string> Video_Quality_Items
{
get { return _Video_Quality_Items; }
set
{
_Video_Quality_Items = value;
OnPropertyChanged("Video_Quality_Items");
}
}
// Selected Item
private string _Video_Quality_SelectedItem { get; set; }
public string Video_Quality_SelectedItem
{
get { return _Video_Quality_SelectedItem; }
set
{
if (_Video_Quality_SelectedItem == value)
{
return;
}
_Video_Quality_SelectedItem = value;
OnPropertyChanged("Video_Quality_SelectedItem");
}
}
// Enabled
private bool _Video_Quality_IsEnabled;
public bool Video_Quality_IsEnabled
{
get { return _Video_Quality_IsEnabled; }
set
{
if (_Video_Quality_IsEnabled == value)
{
return;
}
_Video_Quality_IsEnabled = value;
OnPropertyChanged("Video_Quality_IsEnabled");
}
}
}
You can instantiate an object in xaml:
<Window.DataContext>
<local:MainWindowViewmodel/>
</Window.DataContext>
And you could do that for your usercontrol viewmodels as well.
It's more usual for any child viewmodels to be instantiated in the window viewmodel. Exposed as public properties and the datacontext of a child viewmodel then bound to that property.
I suggest you google viewmodel first and take a look at some samples.
I'm not sure if this is the correct way, but I was able to bind groups of ComboBoxes to different ViewModels.
I created one ViewModel to reference them all.
public class VM: INotifyPropertyChanged
{
...
public static MainViewModel MainView { get; set; } = new MainViewModel ();
public static VideoViewModel VideoView { get; set; } = new VideoViewModel ();
public static AudioViewModel AudioView { get; set; } = new AudioViewModel ();
}
I used Andy's suggestion <local:VM> in MainWindow.xaml.
<Window x:Class="MyProgram.MainWindow"
...
xmlns:local="clr-namespace:MyProgram"
>
<Window.DataContext>
<local:VM/>
</Window.DataContext>
And used a UserControl with DataContext set to VideoView, with ComboBoxes inside.
Instead of a UserControl, can also just use VideoView.Your_Property_Name on each binding.
<UserControl DataContext="{Binding VideoView}">
<StackPanel>
<ComboBox x:Name="cboVideo_Quality"
ItemsSource="{Binding Video_Quality_Items}"
SelectedItem="{Binding Video_Quality_SelectedItem}"
IsEnabled="{Binding Video_Quality_IsEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="105"
Height="22"
Margin="0,0,0,0"/>
<!-- Other ComboBoxes with DataContext VideoView in here -->
</StackPanel>
</UserControl>
Then to access one of the properties:
VM.VideoView.Video_Codec_SelectedItem = "x264";
VM.VideoView.Video_Quality_SelectedItem = "High";
VM.AudioView.Audio_Codec_SelectedItem = "AAC";
VM.AudioView.Audio_Quality_SelectedItem = "320k";
Others have obviously provided good answers, however, the underlying miss of your binding is your first set of DataContext = = = = = to the main view model.
Once you the main form's data context to the MAIN view model, every control there-under is expecting ITS STARTING point as the MAIN view model. Since the MAIN view model does not have a public property UNDER IT of the video and audio view models, it cant find them to bind do.
If you remove the "this.DataContext =", then there would be no default data context and each control SHOULD be able to be bound as you intended them.
So change
this.DataContext =
tbxInput.DataContext =
tbxOutput.DataContext =
cboPreset.DataContext =
MainViewModel.vm;
to
tbxInput.DataContext =
tbxOutput.DataContext =
cboPreset.DataContext =
MainViewModel.vm;

Show new Window with DataContext

I want to simply display my UserControl in a separate window, for example by calling
var windowHandler = new WindowHandler();
windowHandler.Show(new SchoolViewModel);
How do I archive this? I have tried the following:
Set the DataTemplate in App.xaml:
<Application.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type viewModel:SchoolViewModel}">
<view:SchoolUserControl />
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
In code-behind call it:
private void Application_Startup(object sender, StartupEventArgs e)
{
var windowHandler = new WindowHandler();
windowHandler.ShowWindow(new SchoolViewModel(), 200, 200);
}
WindowHandler class:
public class WindowHandler
{
public void ShowWindow(object dataContext, int height, int width)
{
Window window = new Window()
{
DataContext = dataContext,
Width = width,
Height = height
};
window.Show();
}
}
It does show a window, but it's empty. Why is it empty? I also set the DataContext in the UserControl's code-behind:
public SchoolUserControl()
{
InitializeComponent();
DataContext = this;
}
Window is by default templated to show Window.Content and not Window.DataContext. So you should assign whatever you want to show as content:
public class WindowHandler
{
public void ShowWindow(object dataContext, int height, int width)
{
Window window = new Window()
{
Content = dataContext,
Width = width,
Height = height
};
window.Show();
}
}
Also, as others noted, you should remove this line:
DataContext = this;
from your SchoolUserControl, because otherwise you won't have access to the templated view-model from within the control. And since SchoolUserControl is part of a DataTemplate, the templated view-model will be automatically available from SchoolUserControl.DataContext.

Chain of DataBinding

I am trying to do follow DataBinding
Property -> DependencyProperty -> Property
But i have trouble.
For example,
We have simple class with two properties implements INotifyPropertyChanged:
public class MyClass : INotifyPropertyChanged
{
private string _num1;
public string Num1
{
get { return _num1; }
set
{
_num1 = value;
OnPropertyChanged("Num1");
}
}
private string _num2;
public string Num2
{
get { return _num2; }
set
{
_num2 = value;
OnPropertyChanged("Num2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(e));
}
}
And TextBlock declared in xaml:
<TextBlock Name="tb" FontSize="20" Foreground="Red" Text="qwerqwerwqer" />
Now lets trying to bind Num1 to tb.Text:
private MyClass _myClass = new MyClass();
public MainWindow()
{
InitializeComponent();
Binding binding1 = new Binding("Num1")
{
Source = _myClass,
Mode = BindingMode.OneWay
};
Binding binding2 = new Binding("Num2")
{
Source = _myClass,
Mode = BindingMode.TwoWay
};
tb.SetBinding(TextBlock.TextProperty, binding1);
//tb.SetBinding(TextBlock.TextProperty, binding2);
var timer = new Timer(500) {Enabled = true,};
timer.Elapsed += (sender, args) => _myClass.Num1 += "a";
timer.Start();
}
It works well. But if we uncomment this string
tb.SetBinding(TextBlock.TextProperty, binding2);
then TextBlock display nothing. DataBinding doesn't work! How can i to do what i want?
The problem is that the SetBinding call clears out any previous bindings. So when you set a binding to Num2, you are clearing out the binding to Num1. This happens because a dependency property binding cannot have multiple sources- how would it know which one to use? (Of course, this ignores the usage of a MultiBinding, but that's not going to help you in this scenario).
The way you can do this is to make MyClass a DependencyObject and Num1 and Num2 dependency properties. Then you can bind Num2 to the Text property of the TextBox, and Num2 will be updated whenever the text receives an update from Num1.
A picture is worth a thousand words- what you're trying to do is shown on the left. What you need to do is shown on the right:
alt text http://img339.imageshack.us/img339/448/twosources.png
Decided to try this out to ensure my logic was sound, and indeed it works, but there are some tricks. For starters, here is the new MyClass code:
public class MyClass : FrameworkElement
{
public static readonly DependencyProperty Num1Property =
DependencyProperty.Register("Num1", typeof(string), typeof(MyClass));
public static readonly DependencyProperty Num2Property =
DependencyProperty.Register("Num2", typeof(string), typeof(MyClass));
public string Num1
{
get { return (string)GetValue(Num1Property); }
set { SetValue(Num1Property, value); }
}
public string Num2
{
get { return (string)GetValue(Num2Property); }
set { SetValue(Num2Property, value); }
}
}
Nothing scary here, just replaced your INotifyPropertyChanged with DependencyProperty. Now let's check out the window code-behind:
public partial class DataBindingChain : Window
{
public MyClass MyClass
{
get;
set;
}
public DataBindingChain()
{
MyClass = new MyClass();
InitializeComponent();
Binding binding1 = new Binding("Num1")
{
Source = MyClass,
Mode = BindingMode.OneWay
};
Binding binding2 = new Binding("Text")
{
Source = tb,
Mode = BindingMode.OneWay
};
tb.SetBinding(TextBlock.TextProperty, binding1);
MyClass.SetBinding(MyClass.Num2Property, binding2);
var timer = new Timer(500) { Enabled = true, };
timer.Elapsed += (sender, args) => Dispatcher.Invoke(UpdateAction, MyClass);
timer.Start();
}
Action<MyClass> UpdateAction = (myClass) => { myClass.Num1 += "a"; };
}
This is where the magic happens: we set up two bindings. The first binds the TextBlock.Text to Num1, the second binds Num2 to the TextBlock.Text. Now we have a scenario like the right side of the picture I showed you- a data-binding chain. The other magic is that we cannot update the Num1 property on a different thread from the one it was created on- that would create a cross-thread exception. To bypass this, we simply invoke an update onto the UI thread using the Dispatcher.
Finally, the XAML used for demonstration:
<Window x:Class="TestWpfApplication.DataBindingChain"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataBindingChain" Height="300" Width="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Name="tb" Grid.Row="0" FontSize="20" Foreground="Red"/>
<TextBlock Name="tb2" Grid.Row="1" FontSize="20" Foreground="Blue" Text="{Binding MyClass.Num2}"/>
</Grid>
And voila! The finished product:
alt text http://img163.imageshack.us/img163/6114/victorynf.png

Categories

Resources