XAML produces the expected result: a line with rounded ends.
However, data binding the same PathGeometry produces flat ends. I'm not sure why this is, can anyone explain?
Here is a simplified example:
XAML:
<Grid>
<Path Fill="Green" Stroke="Black" StrokeThickness="8"
Stretch="None" IsHitTestVisible="False"
Data="{Binding IndicatorGeometry}"
StrokeStartLineCap="Round" StrokeEndLineCap="Round"/>
<!--<Path Fill="Green" Stroke="Black" StrokeThickness="8"
Stretch="None" IsHitTestVisible="False"
StrokeStartLineCap="Round" StrokeEndLineCap="Round">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="64,64">
<LineSegment Point="128,8"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>-->
</Grid>
C#:
private static PathFigure[] ms_figure = new []
{
new PathFigure(
new Point(64, 64),
new[]
{
new LineSegment(new Point(128, 8), false)
},
true)
};
public PathGeometry IndicatorGeometry
{
get { return (PathGeometry)GetValue(IndicatorGeometryProperty); }
set { SetValue(IndicatorGeometryProperty, value); }
}
public static readonly DependencyProperty IndicatorGeometryProperty =
DependencyProperty.Register("IndicatorGeometry", typeof(PathGeometry), typeof(MainWindow),
new FrameworkPropertyMetadata(new PathGeometry(ms_figure)));
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
If you compare the Geometry created by the XAML to the one created in code behind, then they are different.
The code-behind one has a number of differences...it uses "z" to close the Path, while your XAML one doesn't...also one had a PathFigureCollection the other didn't....also it set the freezable property to true.
You need to try and build the one in the code-behind to be the same as the XAML produced one...it seems like its a lot of work to build the Geometry to match.
I've come up with an alternative way to build the Geometry which works...hopefully this helps in your case.
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 WpfApplication4
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private static Geometry m_DefaultIndicatorGeometry = Geometry.Parse("M 64,64 L 128,8");
public Geometry IndicatorGeometry
{
get { return (Geometry)GetValue(IndicatorGeometryProperty); }
set { SetValue(IndicatorGeometryProperty, value); }
}
public static readonly DependencyProperty IndicatorGeometryProperty =
DependencyProperty.Register("IndicatorGeometry", typeof(Geometry), typeof(MainWindow),
new FrameworkPropertyMetadata(m_DefaultIndicatorGeometry));
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
}
You could alternatively just use a string as the property, because the Data property has a TypeConverter to convert a string that describes a Path using the Path Markup Syntax into a Geometry.
public partial class MainWindow : Window
{
private static string m_DefaultIndicatorGeometry = "M 64,64 L 128,8";
public string IndicatorGeometry
{
get { return (string)GetValue(IndicatorGeometryProperty); }
set { SetValue(IndicatorGeometryProperty, value); }
}
public static readonly DependencyProperty IndicatorGeometryProperty =
DependencyProperty.Register("IndicatorGeometry", typeof(string), typeof(MainWindow),
new FrameworkPropertyMetadata(m_DefaultIndicatorGeometry));
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
Related
<Grid>
<Grid.Background>
<ImageBrush ImageSource="Images\Desert.jpg"
Stretch="UniformToFill" TileMode="Tile"
ViewportUnits="Absolute" Viewport="0,0,1024,768"/>
</Grid.Background>
<Grid.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<RectAnimation Storyboard.TargetProperty="Background.Viewport"
To="-1024,0,1024,768" Duration="0:0:10"
RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
</Grid>
I have this code that scroll a single image in a grid with looping.
Now I have 2 images 1(red) and 2(yellow) am looking something like this.
and it will scroll in loop
You can build a single ImageSource based on multiple images if you wanted to follow your current approach. I have 2 png's (Desert1.png and Desert2.png in an Images folder) and use DataBinding to set the ImageBrush ImageSource to a property defined on the code behind:
<!- Your original xaml ... only difference is the binding -->
<ImageBrush ImageSource="{Binding CombinedImage}"
Stretch="None" TileMode="Tile"
ViewportUnits="Absolute" Viewport="0,0,1024,768"/>
Here's a sample of the code behind (feel free to refactor / use / abuse as you see fit):
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.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xaml;
namespace WpfApplication
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var uriSource1 = new Uri(#"pack://application:,,,/Images/Desert1.png", UriKind.Absolute);
BitmapImage bitmapImage1 = new BitmapImage(uriSource1);
var uriSource2 = new Uri(#"pack://application:,,,/Images/Desert2.png", UriKind.Absolute);
BitmapImage bitmapImage2 = new BitmapImage(uriSource2);
this.DataContext = this;
List<BitmapImage> images = new List<BitmapImage>() { bitmapImage1, bitmapImage2 };
CombinedImage = GetCombinedImage(images);
}
private static RenderTargetBitmap GetCombinedImage(IEnumerable<BitmapImage> images )
{
// Get total width of all images
int totalWidthOfAllImages = images.Sum(p => (int)p.Width);
// Get max height of all images
int maxHeightOfAllImages = images.Max(p => (int)p.Height);
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
double left = 0;
foreach (BitmapImage image in images)
{
drawingContext.DrawImage(image, new Rect(left, 0, image.Width, image.Height));
left += image.Width;
}
}
RenderTargetBitmap bmp = new RenderTargetBitmap(totalWidthOfAllImages, maxHeightOfAllImages, 96, 96, PixelFormats.Default);
bmp.Render(drawingVisual);
return bmp;
}
public ImageSource CombinedImage { get; private set; }
}
}
I have code for image slider. I created using user control for windows phone
Please check with this video http://www.screencast.com/t/XnhHwQFY For first time you need to change logic.
But, I think same code you can use for WPF also
ImageSlider.xaml - Create user control
<UserControl x:Class="ImageSliderDemo.ImageSlider"
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"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
d:DesignHeight="480" d:DesignWidth="480">
<Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}">
<Canvas Height="220" x:Name="imageSliderCanvas" Width="451">
<Image x:Name="imageViewOne"
Canvas.Left="0"
Canvas.Top="0"
Height="220" Width="440" Canvas.ZIndex="9">
<Image.RenderTransform>
<TranslateTransform />
</Image.RenderTransform>
</Image>
<Image x:Name="imageViewTwo"
Canvas.Left="0"
Height="220" Width="440" Canvas.ZIndex="10">
<Image.RenderTransform>
<TranslateTransform />
</Image.RenderTransform>
</Image>
</Canvas>
<StackPanel x:Name="PART_Host" />
</Grid>
</UserControl>
ImageSlider.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using System.Threading;
using System.Windows.Media.Imaging;
using System.Windows.Markup;
namespace ImageSliderDemo
{
public partial class ImageSlider : UserControl
{
private const int LOWER_ZINDEX = 9, UPPER_ZINDEX = 11, POSITION_FROM480 = 480, POSITION_TO0 = 0;
private int nextImage = 1;
#region "Image Slider Properies"
#region "Property - Length Readonly"
public static readonly DependencyProperty LengthProperty = DependencyProperty.Register("Length", typeof(int), typeof(ImageSlider), new PropertyMetadata(0));
public int Length
{
get { return (int)GetValue(LengthProperty); }
private set { SetValue(LengthProperty, value); }
}
#endregion
#region "Property - Begin Delay Readonly"
public static readonly DependencyProperty BeginDelayProperty = DependencyProperty.Register("BeginDelay", typeof(double), typeof(ImageSlider), new PropertyMetadata(5000.00));
public double BeginDelay
{
get { return (double)GetValue(BeginDelayProperty); }
set { SetValue(BeginDelayProperty, value); }
}
#endregion
#region "Property - Animation Duration Readonly"
public static readonly DependencyProperty AnimationDurationProperty = DependencyProperty.Register("AnimationDuration", typeof(double), typeof(ImageSlider), new PropertyMetadata(900.00));
public double AnimationDuration
{
get { return (double)GetValue(AnimationDurationProperty); }
set { SetValue(AnimationDurationProperty, value); }
}
#endregion
#region "Property - Images"
public static readonly DependencyProperty ImagesProperty = DependencyProperty.Register("Images", typeof(List<SliderImage>), typeof(ImageSlider), new PropertyMetadata(null));
public List<SliderImage> Images
{
get { return (List<SliderImage>)GetValue(ImagesProperty); }
set { SetValue(ImagesProperty, value); }
}
#endregion
#endregion
public ImageSlider()
{
InitializeComponent();
}
/// <summary>
/// This Start method used begin the animation
/// </summary>
public void Start()
{
if (this.Images != null)
{
this.Length = this.Images.Count;
hidePrevious(imageViewOne);
showNext(imageViewTwo);
}
else
{
MessageBox.Show("Please add atleast two images");
}
}
#region "Animation methods"
private void showNext(Image imageView)
{
TranslateTransform trans = imageView.RenderTransform as TranslateTransform;
DoubleAnimation animation = new DoubleAnimation();
animation.To = POSITION_TO0;
animation.Duration = TimeSpan.FromMilliseconds(this.AnimationDuration);
animation.From = POSITION_FROM480;
animation.BeginTime = TimeSpan.FromMilliseconds(this.BeginDelay);
Storyboard.SetTarget(animation, trans);
Storyboard.SetTargetProperty(animation, new
PropertyPath(TranslateTransform.XProperty));
// Create storyboard, add animation, and fire it up!
Storyboard storyboard = new Storyboard();
storyboard.Children.Add(animation);
storyboard.Begin();
Canvas.SetZIndex(imageView, UPPER_ZINDEX);
imageView.Visibility = Visibility.Visible;
if (nextImage > this.Length)
{
nextImage = 1;
}
BitmapImage nextBitmapImage = new BitmapImage(new Uri(this.Images[nextImage-1].Path, UriKind.Relative));
imageView.Source = nextBitmapImage;
nextImage++;
}
private void hidePrevious(Image imageView)
{
TranslateTransform trans = imageView.RenderTransform as TranslateTransform;
DoubleAnimation animation = new DoubleAnimation();
animation.To = - POSITION_FROM480;
animation.Duration = TimeSpan.FromMilliseconds(this.AnimationDuration);
animation.From = POSITION_TO0;
animation.BeginTime = TimeSpan.FromMilliseconds(this.BeginDelay);
Storyboard.SetTarget(animation, trans);
Storyboard.SetTargetProperty(animation, new
PropertyPath(TranslateTransform.XProperty));
// Create storyboard, add animation, and fire it up!
Storyboard storyboard = new Storyboard();
storyboard.Children.Add(animation);
storyboard.Begin();
animation.Completed += hideAnimation_Completed;
}
private void hideAnimation_Completed(object sender, EventArgs e)
{
if (Canvas.GetZIndex(imageViewOne) > Canvas.GetZIndex(imageViewTwo))
{
Canvas.SetZIndex(imageViewOne, LOWER_ZINDEX);
hidePrevious(imageViewOne);
showNext(imageViewTwo);
}
else
{
Canvas.SetZIndex(imageViewTwo, LOWER_ZINDEX);
hidePrevious(imageViewTwo);
showNext(imageViewOne);
}
}
#endregion
}
}
Ctrl + B , Just build once
SliderImage.cs -- Add new class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ImageSliderDemo
{
public class SliderImage
{
public string Name { get; set; }
public string Path { get; set; }
public SliderImage(string name, string path)
{
this.Name = name;
this.Path = path;
}
}
}
then do this steps
MainPage.xaml
add at top of xaml page xmlns:local="clr-namespace:[YOUR_PROJECT_NAMESPACE]"
then add just add this below in xaml
<local:ImageSlider x:Name="imageSlider"/>
MainPage.xaml.cs load images
List<SliderImage> images = new List<SliderImage>();
images.Add(new SliderImage("One", "Images/1.png"));
images.Add(new SliderImage("Two", "Images/2.png"));
images.Add(new SliderImage("Three", "Images/3.png"));
images.Add(new SliderImage("Four", "Images/4.png"));
imageSlider.Images = images;
imageSlider.Start();
Note : I used ImageSliderDemo as my namespace. If your using different please make sure you updated in user control as well. And I found only first time it is show same image twice. you can change the logic in ImageSlider.xaml.cs file
I learn bindings in WPF via book. I have wrote such code:
using System;
namespace WpfBinding {
enum SomeColors {
Red,
Green,
Blue,
Gray
}
}
and
using System;
namespace WpfBinding {
class TestItem {
SomeColors color;
public TestItem(SomeColors color) {
Color = color;
}
internal SomeColors Color {
get { return color; }
set { color = value; }
}
public override string ToString() {
return Color.ToString();
}
}
}
XAML of my Window:
<Window x:Class="WpfBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListBox x:Name="listBox" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Margin="5"/>
<ComboBox x:Name="comboBox" HorizontalAlignment="Stretch"
VerticalAlignment="Top" Margin="5" Grid.Column="1"/>
</Grid>
</Window>
I have tried create binding through code:
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfBinding {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
// Data for listbox
TestItem[] items = new TestItem[] {
new TestItem(SomeColors.Red),
new TestItem(SomeColors.Green),
new TestItem(SomeColors.Green),
new TestItem(SomeColors.Red),
new TestItem(SomeColors.Blue),
new TestItem(SomeColors.Red),
};
// Create ObservableCollection item
ObservableCollection<TestItem> collection = new ObservableCollection<TestItem>(items);
listBox.ItemsSource = collection;// set data for listbox
comboBox.ItemsSource = Enum.GetValues(typeof(SomeColors)); // Get items from my enum
// Create bindings
Binding bind = new Binding();
bind.Source = listBox;
bind.Path = new PropertyPath("SelectedItem.Color");
bind.Mode = BindingMode.TwoWay;
comboBox.SetBinding(ComboBox.SelectedItemProperty, bind);
}
}
}
But my binding ain't working. Why?
Screen:
Here is the error -
System.Windows.Data Error: 40 : BindingExpression path error: 'Color' property
not found on 'object' ''TestItem' (HashCode=13974362)'.
BindingExpression:Path=SelectedItem.Color; DataItem='ListBox' (Name='listBox');
target element is 'ComboBox' (Name='comboBox'); target property is 'SelectedItem'
(type 'Object')
You need to make the property Color public instead of internal.
From MSDN here -
The properties you use as binding source properties for a binding must
be public properties of your class. Explicitly defined interface
properties cannot be accessed for binding purposes, nor can protected,
private, internal, or virtual properties that have no base
implementation.
It's always useful to watch the Output window of Visual Studio when debugging! Had you looked there, you'd have seen this:
System.Windows.Data Error: 40 : BindingExpression path error: 'Color' property not found on 'object' ''TestItem' (HashCode=20856310)'. BindingExpression:Path=SelectedItem.Color; DataItem='ListBox' (Name='listBox'); target element is 'ComboBox' (Name='comboBox'); target property is 'SelectedItem' (type 'Object')
Exactly, binding can be done with public properties only, so
internal SomeColors Color
should be
public SomeColors Color
I think the problem is that your classes are not implementing INotifyPropertyChanged.
In order for the bindings to know when a property has changed it's value, you have to send it notification, and you do that with INotifyPropertyChanged.
UPDATE
So your listbox is bound to an ObservableCollection which does provide change notifiations, but only to the list box and only if you add or remove items from the collections.
You might also want to enable WPF Binding trace information in visual studio (http://msdn.microsoft.com/en-us/library/dd409960%28v=vs.100%29.aspx) that might help you figure out what is going on too.
The last thing I noticed is that the Color property of your TestItem class is marked as internal. WPF won't have access to that property unless it's public.
Thanks all. I have edit my code:
using System;
using System.ComponentModel;
namespace WpfBinding {
public class TestItem : INotifyPropertyChanged{
SomeColors color;
public TestItem(SomeColors color) {
Color = color;
}
public SomeColors Color {
get { return color; }
set { color = value;
OnPropertyChanged("Color");
}
}
public override string ToString() {
return Color.ToString();
}
void OnPropertyChanged(String name) {
PropertyChangedEventHandler temp = PropertyChanged;
if (null != temp) {
temp(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
and
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
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 WpfBinding {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
// Data for listbox
TestItem[] items = new TestItem[] {
new TestItem(SomeColors.Red),
new TestItem(SomeColors.Green),
new TestItem(SomeColors.Green),
new TestItem(SomeColors.Red),
new TestItem(SomeColors.Blue),
new TestItem(SomeColors.Red),
};
// Create ObservableCollection item
ObservableCollection<TestItem> collection = new ObservableCollection<TestItem>(items);
listBox.ItemsSource = collection;// set data for listbox
ObservableCollection<SomeColors> collection2 = new
ObservableCollection<SomeColors>(Enum.GetValues(typeof(SomeColors)).Cast<SomeColors>());
comboBox.ItemsSource = collection2; // Get items from my enum
// Create bindings
Binding bind = new Binding();
bind.Source = listBox;
bind.Path = new PropertyPath("SelectedItem.Color");
bind.Mode = BindingMode.TwoWay;
comboBox.SetBinding(ComboBox.SelectedItemProperty, bind);
}
}
}
Look Screen, please:
I am trying to open HTML file in WebBrowser window of WPF using MVVM patten.
Note: I have fixed the issues i was getting. Now this code works as desired.
ViewHTMLPageView.xaml
<Window x:Class="MyProject.ViewHTMLPageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject.Utility"
Title="HTML Report" Height="454" Width="787"
>
<Grid Name="grid1">
<WebBrowser local:WebBrowserUtility.BindableSource="{Binding ReportPage}" />
</Grid>
</Window>
ViewHTMLPageViewModel.cs
namespace MyProject
{
public class ViewHTMLPageViewModel: ViewModelBase
{
public ViewHTMLPageView()
{
//Testing html page on load
_reportPage = "<table border='5'><tr><td> This is sample <b> bold </b></td></tr></table>";
OnPropertyChanged("ReportPage");
}
private string _reportPage;
public string ReportPage
{
get
{
return _reportPage;
}
set
{
if (_reportPage != value)
{
_reportPage = value;
OnPropertyChanged("ReportPage");
}
}
}
}
WebBrowserUtility.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
namespace MyProject.Utility
{
public static class WebBrowserUtility
{
public static readonly DependencyProperty BindableSourceProperty =
DependencyProperty.RegisterAttached("BindableSource", typeof(string),
typeof(WebBrowserUtility), new UIPropertyMetadata(null,
BindableSourcePropertyChanged));
public static string GetBindableSource(DependencyObject obj)
{
return (string)obj.GetValue(BindableSourceProperty);
}
public static void SetBindableSource(DependencyObject obj, string value)
{
obj.SetValue(BindableSourceProperty, value);
}
public static void BindableSourcePropertyChanged(DependencyObject o,
DependencyPropertyChangedEventArgs e)
{
var webBrowser = (WebBrowser)o;
webBrowser.NavigateToString((string)e.NewValue);
}
}
}
In WebBrowserUtility.cs, change the following statement:
using System.Windows.Forms;
to
using System.Windows.Controls;
Now, for +1 on your question, can you tell me why this fixes your problem?
I am trying to bind an observable collection to a user control but it is not getting updated on user change but it is getting updated when the user control is changed through code. Following is an example i tried. It might be a bit long but it is working so you can copy and paste the code as it is.
Please see my question at the end of the post.
--Customer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace TestMVVM
{
class Customer : INotifyPropertyChanged
{
private string firstName;
private string lastName;
public string FirstName
{
get { return firstName; }
set
{
if (firstName != value)
{
firstName = value;
RaisePropertyChanged("FirstName");
}
}
}
public string LastName
{
get { return lastName; }
set
{
if (lastName != value)
{
lastName = value;
RaisePropertyChanged("LastName");
}
}
}
#region PropertChanged Block
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(property));
}
}
#endregion
}
}
--UCTextBox.xaml
<UserControl x:Class="TestMVVM.UCTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="40" Width="200">
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Margin="10,10,0,0" Name="txtTextControl"
VerticalAlignment="Top" Width="120" />
</Grid>
--UCTextBox.xaml.cs
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;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace TestMVVM
{
///
/// Interaction logic for UCTextBox.xaml
///
public partial class UCTextBox : UserControl, INotifyPropertyChanged
{
public UCTextBox()
{
InitializeComponent();
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(UCTextBox),
new UIPropertyMetadata(string.Empty, new PropertyChangedCallback(textChangedCallBack)));
static void textChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
{
UCTextBox pasTextBox = (UCTextBox)property;
pasTextBox.txtTextControl.Text = (string)args.NewValue;
}
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
NotifyPropertyChanged("Text");
}
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
-- Window1.xaml
<Window x:Class="TestMVVM.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestMVVM"
Title="Window1" Height="300" Width="300">
<Grid>
<local:UCTextBox x:Name="txtUC" />
<Button Height="23" HorizontalAlignment="Left" Margin="39,0,0,82"
Name="btnUpdate" VerticalAlignment="Bottom" Width="75" Click="btnUpdate_Click">Update</Button>
<Button Height="23" Margin="120,0,83,82" Name="btnChange" VerticalAlignment="Bottom" Click="btnChange_Click">Change</Button>
</Grid>
-- Window1.xaml.cs
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;
using System.Collections.ObjectModel;
namespace TestMVVM
{
///
/// Interaction logic for Window1.xaml
///
public partial class Window1 : Window
{
CustomerHeaderViewModel customerHeaderViewModel = null;
public Window1()
{
InitializeComponent();
customerHeaderViewModel = new CustomerHeaderViewModel();
customerHeaderViewModel.LoadCustomers();
txtUC.DataContext = customerHeaderViewModel.Customers[0];
Binding binding = new Binding();
binding.Source = customerHeaderViewModel.Customers[0];
binding.Path = new System.Windows.PropertyPath("FirstName");
binding.Mode = BindingMode.TwoWay;
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
txtUC.SetBinding(UCTextBox.TextProperty, binding);
}
private void btnUpdate_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(customerHeaderViewModel.Customers[0].FirstName);
}
private void btnChange_Click(object sender, RoutedEventArgs e)
{
txtUC.Text = "Tom";
}
}
class CustomerHeaderViewModel
{
public ObservableCollection Customers { get; set; }
public void LoadCustomers()
{
ObservableCollection customers = new ObservableCollection();
customers.Add(new Customer { FirstName = "Jim", LastName = "Smith", NumberOfContracts = 23 });
Customers = customers;
}
}
}
When i run the Window1.xaml my user control shows the data as "Jim". Now when i change the text to say "John" and click on Update, the messagebox still shows "Jim" that means the observable collection is not getting updated. When i click on Change button the user control changes the data to "Tom". Now when i click on Update button the Messagebox shows "Tom". Can anyone please tell me how to achieve the updation of observable collection by changing the data in user control rather than through code?
That's because you're not handling the txtTextControl.TextChanged event, so your Text dependency property is never updated.
Anyway, you don't need to handle that manually with a DependencyPropertyChangedCallback and an event handler, you can just bind the txtTextControl.Text to the Text dependency property :
<TextBox Height="23" HorizontalAlignment="Left" Margin="10,10,0,0" Name="txtTextControl"
VerticalAlignment="Top" Width="120"
Text="{Binding Path=Text, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:UCTextBox}}}"/>
An observable collection, observers the collection only. You will get notified when items are added or deleted not when fields of a single items did change. That is something completely different. Like Thomas Levesque said, you just need to bind the right property.
I have a problem updating the WPF Designer when binding to custom dependency properties.
In the following example, I create a simple Ellipse that I would like to fill with my custom MyAwesomeFill property. The MyAwesomeFill has a default value of a Yellow SolidColor brush.
The problem is that in the control form of the designer I cannot see the default fill of the ellipse (Yellow), instead the ellipse is filled with SolidColor (#00000000). However, when I run the application everything works PERFECTLY.
Do you have any ideas why this may be happenning?
Thanks.
Here's the code that I use:
XAML:
<UserControl x:Class="TestApplication.MyEllipse"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Grid>
<Ellipse Stroke="Black" StrokeThickness="5" Fill="{Binding MyAwesomeFill}"></Ellipse>
</Grid>
</UserControl>
C#:
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 TestApplication
{
public partial class MyEllipse : UserControl
{
#region Dependency property MyAwesomeFill
//Define and register dependency property
public static readonly DependencyProperty MyAwesomeFillProperty = DependencyProperty.Register(
"MyAwesomeFill",
typeof(Brush),
typeof(MyEllipse),
new PropertyMetadata(new SolidColorBrush(Colors.Yellow), new PropertyChangedCallback(OnMyAwesomeFillChanged))
);
//property wrapper
public Brush MyAwesomeFill
{
get { return (Brush)GetValue(MyAwesomeFillProperty); }
set { SetValue(MyAwesomeFillProperty, value); }
}
//callback
private static void OnMyAwesomeFillChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
MyEllipse m = (MyEllipse)obj;
m.OnMyAwesomeFillChanged(e);
}
#endregion
//callback
protected virtual void OnMyAwesomeFillChanged(DependencyPropertyChangedEventArgs e)
{
}
public MyEllipse()
{
InitializeComponent();
DataContext = this;
}
}
}
Code behind is not guaranteed to be run by the designer. If you add your MyEllipse control to a window it will run (ellipse in window has yellow background) but not when you look at the control directly. This means it will work for users of your control which is what is important.
To fix it to look good when opening up MyEllipse in the designer, add a fallback value.
<Ellipse
Stroke="Black"
StrokeThickness="5"
Fill="{Binding MyAwesomeFill, FallbackValue=Yellow}">
</Ellipse>