How would someone go about adding check boxes to only the children of a tree view in XAML? My goal is to have a tree view where the parent is just a text block and all the children are check boxes but only one child can be checked at a time. I have no problem making the whole the tree view check boxes but I am not sure how to get what I really want Any suggestions?
Thanks.
Why don't you just do it in code? Like this:
TreeViewItem newItem = new TreeViewItem()
{
Header = "One"
};
treeViewObjects.Items.Add(newItem);
TreeViewItem newItem1 = new TreeViewItem()
{
Header = new CheckBox()
{
Content = "Two"
}
};
newItem.Items.Add(newItem1);
Try this Working with Checkboxes in the WPF TreeView.
The easiest way to do this is to shape your data so that the tree view can represent it in the way you've described. Here is an example of a minimal data structure that corresponds to your type of tree:
public class CheckedList
{
public string Title { get; set; }
public ObservableCollection<CheckedItem> Items { get; private set; }
public CheckedList()
{
Items = new ObservableCollection<CheckedItem>();
//DEBUG: Test data
Title = "Test Title";
Items.Add(new CheckedItem("Item 1", true));
Items.Add(new CheckedItem("Item 2", false));
}
}
public class CheckedItem : DependencyObject
{
public static readonly DependencyProperty StateProperty =
DependencyProperty.Register("StateProperty", typeof(bool), typeof(CheckedItem), new UIPropertyMetadata(false));
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("TextProperty", typeof (string), typeof (CheckedItem), new UIPropertyMetadata(string.Empty));
public bool State
{
get { return (bool)GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}
public string Text
{
get { return (string) GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public CheckedItem(string text, bool state)
{
Text = text;
State = state;
}
}
Here is XAML and code-behind for a window and tree view with data templates to represent the data as text headers with check box items:
<Window x:Class="TestApp.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestApp"
Title="Window1" Height="300" Width="300" Loaded="Window_Loaded">
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:CheckedList}" ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Title}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:CheckedItem}">
<CheckBox Content="{Binding Text}" IsChecked="{Binding State, Mode=TwoWay}"></CheckBox>
</DataTemplate>
</Window.Resources>
<Grid>
<TreeView x:Name="ExampleTree"></TreeView>
</Grid>
The code-behind:
public partial class Window1 : Window
{
ObservableCollection<CheckedList> _lists = new ObservableCollection<CheckedList>();
public Window1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//DEBUG: Test data
_lists.Add(new CheckedList());
_lists.Add(new CheckedList());
ExampleTree.ItemsSource = _lists;
}
}
Using ObservableCollection and DependencyObject allows the tree and data structure to stay in sync. As the user clicks on items in the tree, you should be able to look at your lists and see the modifications. Alternately, if you modify the data, it should be reflected in the tree.
Related
I am using the WPF AutoCompleteBox and I have it working great, but one thing I would like to do is sort the suggestion list on the fly after each letter is entered into the primary TextBox. Does anyone know how to do this? I tried using an ICollectionView property with the DefaultView logic and adding SortDescriptions but it doesn't seem to phase the suggestion list. To make sure my collection view sorting was working I put a normal ListBox control and an AutoCompleteBox control on the same window and bound both controls to the same observable collection with the same collection view and the normal ListBox control showed the items sorted correctly using the SortDescriptions, but the AutoCompleteBox list didn't have the items sorted. It had them in the order they were added to the collection.
Thoughts? Suggestions? Has anyone done this?
I have no idea how #user1089031 done this, but here is working sample for anyone who could be interested in (updated to #adabyron's comment!):
ViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;
namespace WpfApplication12
{
public class Item
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
public class ViewModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate {};
private readonly ObservableCollection<Item> source;
private readonly ICollectionView items;
private string searchText;
public ViewModel()
{
source = new ObservableCollection<Item>
{
new Item {Name = "111111111 Test abb - (1)"},
new Item {Name = "22222 Test - (2)"},
new Item {Name = "333 Test - (3)"},
new Item {Name = "44444 Test abc - (4)"},
new Item {Name = "555555 Test cde - (5)"},
new Item {Name = "66 Test - bbcd (6)"},
new Item {Name = "7 Test - cd (7)"},
new Item {Name = "Test - ab (8)"},
};
items = new ListCollectionView(source);
}
public ICollectionView Items
{
get { return items; }
}
public IEnumerable<Item> ItemsSorted
{
get
{
return string.IsNullOrEmpty(SearchText)
? source
: (IEnumerable<Item>)source
.OrderBy(item => item.Name.IndexOf(SearchText,
StringComparison.InvariantCultureIgnoreCase));
}
}
public Item Selected { get; set; }
public string SearchText
{
get { return searchText; }
set
{
searchText = value;
PropertyChanged(this,
new PropertyChangedEventArgs("SearchText"));
PropertyChanged(this,
new PropertyChangedEventArgs("ItemsSorted"));
}
}
}
}
MainWindow.xaml:
<Window x:Class="WpfApplication12.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit"
xmlns:wpfApplication2="clr-namespace:WpfApplication12"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Title="MainWindow" Height="200" Width="500"
DataContext="{DynamicResource viewModel}">
<Window.Resources>
<wpfApplication2:ViewModel x:Key="viewModel" />
<DataTemplate DataType="{x:Type wpfApplication2:Item}">
<TextBlock Text="{Binding Name}" FontFamily="Courier New" />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<controls:AutoCompleteBox
ItemsSource="{Binding ItemsSorted}"
FilterMode="ContainsOrdinal"
SelectedItem="{Binding Selected, Mode=TwoWay}"
MinimumPrefixLength="0"
VerticalAlignment="Top" Margin="5">
<i:Interaction.Behaviors>
<wpfApplication2:SearchTextBindBehavior
BoundSearchText="{Binding SearchText,
Mode=OneWayToSource}" />
</i:Interaction.Behaviors>
</controls:AutoCompleteBox>
<ListBox Grid.Column="1"
ItemsSource="{Binding Items}" Margin="5" />
</Grid>
</Window>
As you could notice I've add one custom behavior to AutoCompleteBox control:
<i:Interaction.Behaviors>
<wpfApplication2:SearchTextBindBehavior
BoundSearchText="{Binding SearchText,
Mode=OneWayToSource}" />
</i:Interaction.Behaviors>
This is because AutoCompleteBox's own SearchText property is read-only. So here is the code of this behavior:
SearchTextBindBehavior.cs (Updated)
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace WpfApplication12
{
public class SearchTextBindBehavior : Behavior<AutoCompleteBox>
{
public static readonly DependencyProperty BoundSearchTextProperty =
DependencyProperty.Register("BoundSearchText",
typeof(string), typeof(SearchTextBindBehavior));
public string BoundSearchText
{
get { return (string)GetValue(BoundSearchTextProperty); }
set { SetValue(BoundSearchTextProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.TextChanged += OnTextChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.TextChanged -= OnTextChanged;
}
private void OnTextChanged(object sender, RoutedEventArgs args)
{
if(AssociatedObject.Text.Length == 0)
{
BoundSearchText = string.Empty;
return;
}
if(AssociatedObject.SearchText ==
AssociatedObject.Text.Substring(0,
AssociatedObject.Text.Length - 1))
{
BoundSearchText = AssociatedObject.Text;
}
}
}
}
Note: To make it all work you will need to add reference to the System.Windows.Interactivity.dll from the Expression Blend 4 SDK. This is just where Behavior<T> and a few its friends live.
If you have Expression Blend already installed, you already have all the SDK and there is no need to download anything. Just in case - on my machine the assembly located here:
C:\Program Files\Microsoft SDKs\Expression\Blend.NETFramework\v4.0\Libraries\System.Windows.Interactivity.dll
And, finally, if you have some good reason to do NOT add reference to this popular official library, feel free to re-implemented this custom behavior in "the old way" via plain old attached properties.
Hope that helps.
This is what I ended up with, a slight adaptation of Sevenate's answer, so if you wanted to upvote, do that to his post.
I used a subclass (I had the AutoCompleteBox subclassed already for other reasons), which allows me to create a wrapper dependency property to get the readonly SearchText (=what the user entered via keyboard) to the ViewModel - instead of a blend behavior, which is a perfectly valid way, too.
The crux of the matter is that you should only apply the dynamic sorting upon changes of SearchText, not Text (=what is displayed in the AutoCompleteBox, will also change if a suggestion is selected in the dropdown). Sevenate's way to raise the PropertyChanged event of the readonly ItemsSource (ItemsSorted) is a nice and clean way to apply the sorting.
ViewModel:
public class Item
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
public class AutoCompleteBoxDynamicSortingVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private readonly ObservableCollection<Item> source;
public AutoCompleteBoxDynamicSortingVM()
{
source = new ObservableCollection<Item>
{
new Item {Name = "111111111 Test abb - (1)"},
new Item {Name = "22222 Test - (2)"},
new Item {Name = "333 Test - (3)"},
new Item {Name = "44444 Test abc - (4)"},
new Item {Name = "555555 Test cde - (5)"},
new Item {Name = "66 Test - bbcd (6)"},
new Item {Name = "7 Test - cd (7)"},
new Item {Name = "Test - ab (8)"},
};
}
public IEnumerable<Item> ItemsSorted
{
get
{
return string.IsNullOrEmpty(Text) ? (IEnumerable<Item>)source :
source.OrderBy(item => item.Name.IndexOf(Text, StringComparison.OrdinalIgnoreCase));
}
}
public Item Selected { get; set; }
// Text that is shown in AutoCompleteBox
private string text;
public string Text
{
get { return text; }
set { text = value; OnPropertyChanged("Text"); }
}
// Text that was entered by user (cannot be changed from viewmodel)
private string searchText;
public string SearchText
{
get { return searchText; }
set
{
searchText = value;
OnPropertyChanged("SearchText");
OnPropertyChanged("ItemsSorted");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Subclass of AutoCompleteBox:
public class MyAutoCompleteBox : AutoCompleteBox
{
/// <summary>
/// Bindable property that encapsulates the readonly property SearchText.
/// When the viewmodel tries to set SearchText by way of EnteredText, it will fail without an exception.
/// </summary>
public string EnteredText
{
get { return (string)GetValue(EnteredTextProperty); }
set { SetValue(EnteredTextProperty, value); }
}
public static readonly DependencyProperty EnteredTextProperty = DependencyProperty.Register("EnteredText", typeof(string), typeof(MyAutoCompleteBox), new PropertyMetadata(null));
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
// synchronize SearchText and EnteredText (only one-way)
if (e.Property == AutoCompleteBox.SearchTextProperty && this.EnteredText != this.SearchText)
EnteredText = SearchText;
base.OnPropertyChanged(e);
}
}
Xaml:
<UserControl x:Class="WpfApplication1.Controls.AutoCompleteBoxDynamicSorting"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myctrls="clr-namespace:WpfApplication1.Controls"
xmlns:models="clr-namespace:WpfApplication1.ViewModels"
Height="350" Width="525"
DataContext="{DynamicResource viewModel}">
<UserControl.Resources>
<models:AutoCompleteBoxDynamicSortingVM x:Key="viewModel" />
<DataTemplate DataType="{x:Type models:Item}">
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</UserControl.Resources>
<Grid>
<myctrls:MyAutoCompleteBox
ItemsSource="{Binding ItemsSorted}"
Text="{Binding Text, Mode=TwoWay}"
EnteredText="{Binding SearchText, Mode=OneWayToSource}"
FilterMode="ContainsOrdinal"
VerticalAlignment="Top" Margin="5" />
</Grid>
</UserControl>
I'm working on a WPF MVVM application. I'm looking to databind a WebBrowser control to a view model which is in turn bound to a Tab. Following the advice in this article, I created a static helper class consisting of a static DependancyProperty:
public static class WebBrowserHelper
{
public static readonly DependencyProperty BodyProperty =
DependencyProperty.RegisterAttached("Body", typeof(string), typeof(WebBrowserHelper), new PropertyMetadata(OnBodyChanged));
public static string GetBody(DependencyObject dependencyObject)
{
return (string)dependencyObject.GetValue(BodyProperty);
}
public static void SetBody(DependencyObject dependencyObject, string body)
{
dependencyObject.SetValue(BodyProperty, body);
}
private static void OnBodyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
string newValue = (string)e.NewValue;
var webBrowser = (WebBrowser)d;
webBrowser.NavigateToString(newValue);
}
}
XAML Binding WebBrowser to DependancyProperty:
<WebBrowser Grid.Column="2" HorizontalAlignment="Center" src:WebBrowserHelper.Body="{Binding HTMLBody}" VerticalAlignment="Center" Height="Auto" Width="Auto" />
ViewModel that bound to ItemsSource of Tab Control:
public class SomeVM : ViewModelBase, INotifyPropertyChanged
{
private string _htmlBody;
private SomeView _myView = new SomeView();
public SomeVM (string tabName)
{
TabName = tabName;
string contentsAsHTML = do_a_whole_bunch_of_stuff_to_generate_an_HTML_string();
HTMLBody = contentsAsHTML;
}
public string HTMLBody
{
get { return _htmlBody; }
set
{
if (_htmlBody != value)
{
_htmlBody = value;
RaisePropertyChanged("HTMLBody");
}
}
}
public SomeView View
{
get {return _myView;}
set { }
}
public string TabName { get; set; }
}
MainViewModel, Creating the Tab collection:
private ObservableCollection<SomeVM> _tabs;
public ObservableCollection<SomeVM> Tabs
{
get
{
if (_tabs== null)
{
_tabs= new ObservableCollection<SomeVM>();
_tabs.Add(new SomeVM("Tab 1"));
_tabs.Add(new SomeVM("Tab 2"));
_tabs.Add(new SomeVM("Tab 3"));
}
return _tabs;
}
}
MainWindow.xaml setting up the Tab Binding:
<TabControl ItemsSource="{Binding Tabs, Source={StaticResource vm}}"
>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding TabName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding View}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
My problem is that "OnBodyChanged" is fired multiple times on ever tab change. The HTML takes a few seconds to load, and I would rather it only loads when the property is actually modified in the viewmodel.
EDIT
Here's the smallest sample project that recreates my problem.
Your problem is not relevant to attached properties or MVVM.
In fact, the real problem is that TabControl destroy and recreate its child every time you change the selected tab. That would explain why the handler is invoked more than once. The VisualTree only contains the selected Tab.
If you can try with another control, you will see there are no errors.
For solving this issue, I will redirect you to this post.
I'm new to Prism, but I have successfully built several WPF/Mvvm-Light applications. I'm using ViewModel-first instaciation for each View/ViewModel pair. The views are all loaded and deactivated when the application opens. Views are activated as a result of catching an aggregate event aimed at them. This is the first view I've tried to bind to data in a ViewModel. The view displays as expected, except that my listbox is never populated. Only the outline of the listbox is visible. If I change the background color of the listbox, the color of the empty listbox is changed. The ViewModel property has eight rows but none of them are visible. I am able to display hardcoded items in the list box. I know that the view model is loading into the view as the data context, since another textblock is able to bind to a ViewModel property It must be something broken in my listbox xaml. Here is some xaml to review:
<UserControl
x:Class="DxStudioSelect.View.DxStudioFindView"
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"
mc:Ignorable="d"
>
<UserControl.Resources>
<DataTemplate x:Key="DxStudioListTemplate">
<TextBlock Text="{Binding Path=FriendlyForkName}"/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ListBox
Grid.Column="0"
ItemsSource="{Binding DatabaseInstanceList}"
ItemTemplate="{StaticResource DxStudioListTemplate}"
/>
<TextBlock Text="{Binding Path=PageName}" Grid.Column="1" FontSize="32" Foreground="Green" TextAlignment="Right"/>
</Grid>
</UserControl>
Here is the code-behind:
public partial class DxStudioFindView : UserControl, IDxStudioFindView {
public DxStudioFindView() {
InitializeComponent();
}
public IViewModel ViewModel {
get { return (IDxStudioFindViewModel)DataContext; }
set { DataContext = value; }
}
}
Here is the ViewModel:
private readonly IEventAggregator _eventAggregator;
private readonly IUnityContainer _unityContainer;
private readonly IRegionManager _regionManager;
private readonly string _dxStudioDatabaseName;
private readonly HeaderUpdatePayload _headerUpdatePayload = new HeaderUpdatePayload("DxStudio", "Select DxStudio Instance");
public DxStudioFindViewModel(IUnityContainer unityContainer, IRegionManager regionManager, IEventAggregator eventAggregator, IDxStudioFindView view)
: base(view) {
_unityContainer = unityContainer;
_regionManager = regionManager;
_eventAggregator = eventAggregator;
View.ViewModel = this;
if(IsInDesignMode) {
//Design-time, so show fake data
DesignTimeDataLoad();
} else {
//Run-time, so do the real stuff
DesignTimeDataLoad();
_dxStudioDatabaseName = LiteralString.DxStudioDatabaseNameTest;
_eventAggregator.GetEvent<ViewChangeRequestEvent>().Subscribe(DxStudioInstanceChangeRequest, ThreadOption.UIThread, false, target => target.TargetView == LiteralString.DxStudioFind);
}
}
public string PageName { get; set; }
//public string PageName { get { return "Find DxStudio Instance"; } }
private ObservableCollection<IDxStudioInstanceDto> _dxStudioInstanceList = null;
public ObservableCollection<IDxStudioInstanceDto> DxStudioInstanceList {
get { return _dxStudioInstanceList; }
set {
_dxStudioInstanceList = value;
OnPropertyChanged("DxStudioInstanceList");
}
}
private void DxStudioInstanceChangeRequest(ViewChangeRequestPayload payload) {
var region = _regionManager.Regions[RegionNames.Content];
region.Activate(View);
_eventAggregator.GetEvent<ViewChangedHeaderEvent>().Publish(_headerUpdatePayload);
var footerUpdatePayload = new FooterUpdatePayload(FooterDisplayMode.DxStudioSelect, _dxStudioDatabaseName, payload.TargetBackDatabase, payload.TargetBack, string.Empty, LiteralString.ToolboxStart);
_eventAggregator.GetEvent<ViewChangedFooterEvent>().Publish(footerUpdatePayload);
}
private void DesignTimeDataLoad() {
PageName = "Find DxStudio Instance";
DxStudioInstanceList = new ObservableCollection<IDxStudioInstanceDto>() {
new DxStudioInstanceDto("Instance1"),
new DxStudioInstanceDto("Instance2"),
new DxStudioInstanceDto("Instance3"),
new DxStudioInstanceDto("Instance4"),
new DxStudioInstanceDto("Instance5"),
new DxStudioInstanceDto("Instance6"),
new DxStudioInstanceDto("Instance7"),
new DxStudioInstanceDto("Instance8"),
};
}
And here is the data transfer object:
public class DxStudioInstanceDto : IDxStudioInstanceDto {
public string FriendlyForkName { get; private set; }
public DxStudioInstanceDto(string friendlyForkName) { FriendlyForkName = friendlyForkName; }
}
Since I'm completely out of ideas, any suggestion would be helpful.
Thanks
Your list is binding to ItemsSource="{Binding DatabaseInstanceList}" but your view model has the property DxStudioInstanceList.
I have an ObservableCollection of "Layouts" and a "SelectedLocation" DependencyProperty on a Window. The SelectedLocation has a property called "Layout", which is an object containing fields like "Name" etc. I'm trying to bind a combobox to the SelectedLayout but it's not working.
The following does not work, I've tried binding to SelectedItem instead to no avail. I believe it may be something to do with the fact that I'm binding to a subProperty of the SelectedLocation DependencyProperty (though this does implement INotifyPropertyChanged.
<ComboBox Grid.Row="2" Grid.Column="0" x:Name="cboLayout" ItemsSource="{Binding Layouts,ElementName=root}" SelectedValue="{Binding SelectedLocation.Layout.LayoutID,ElementName=root}" DisplayMemberPath="{Binding Name}" SelectedValuePath="LayoutID" />
However, the following works (Also bound to the "SelectedLocation" DP:
<TextBox Grid.Row="4" Grid.Column="1" x:Name="txtName" Text="{Binding SelectedLocation.Name,ElementName=root,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
What type property Layouts has? I suppose something like this this: IEnumerable<Layout>.
But you bind selected value to Layout.LayoutID. So you got situation, when combo box contains Layout objects, and you try to select it by Int identifier. Of course binding engine can't find any Int there.
I have no idea about details of your code, so one thing I could propose: try to reduce your binding expression: SelectedItem="{Binding SelectedLocation.Layout,ElementName=root}.
If no success, provide more code to help me understand what's going on.
====UPDATE====
As I've said, you are obviously doing something wrong. But I am not paranormalist and couldn't guess the reason of your fail (without your code). If you don't want to share your code, I decided to provide simple example in order to demonstrate that everything works. Have a look at code shown below and tell me what is different in your application.
Class Layout which exposes property LayoutId:
public class Layout
{
public Layout(string id)
{
this.LayoutId = id;
}
public string LayoutId
{
get;
private set;
}
public override string ToString()
{
return string.Format("layout #{0}", this.LayoutId);
}
}
Class SelectionLocation which has nested property Layout:
public class SelectedLocation : INotifyPropertyChanged
{
private Layout _layout;
public Layout Layout
{
get
{
return this._layout;
}
set
{
this._layout = value;
this.OnPropertyChanged("Layout");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
var safeEvent = this.PropertyChanged;
if (safeEvent != null)
{
safeEvent(this, new PropertyChangedEventArgs(name));
}
}
}
And Window class with dependency properties (actually, in my example StartupView is UserControl, but it doesn't matter):
public partial class StartupView : UserControl
{
public StartupView()
{
InitializeComponent();
this.Layouts = new Layout[] { new Layout("AAA"), new Layout("BBB"), new Layout("CCC") };
this.SelectedLocation = new SelectedLocation();
this.SelectedLocation.Layout = this.Layouts.ElementAt(1);
}
public IEnumerable<Layout> Layouts
{
get
{
return (IEnumerable<Layout>)this.GetValue(StartupView.LayoutsProperty);
}
set
{
this.SetValue(StartupView.LayoutsProperty, value);
}
}
public static readonly DependencyProperty LayoutsProperty =
DependencyProperty.Register("Layouts",
typeof(IEnumerable<Layout>),
typeof(StartupView),
new FrameworkPropertyMetadata(null));
public SelectedLocation SelectedLocation
{
get
{
return (SelectedLocation)this.GetValue(StartupView.SelectedLocationProperty);
}
set
{
this.SetValue(StartupView.SelectedLocationProperty, value);
}
}
public static readonly DependencyProperty SelectedLocationProperty =
DependencyProperty.Register("SelectedLocation",
typeof(SelectedLocation),
typeof(StartupView),
new FrameworkPropertyMetadata(null));
}
XAML of StartupView:
<UserControl x:Class="Test.StartupView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:self="clr-namespace:HandyCopy"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Root">
<WrapPanel>
<ComboBox ItemsSource="{Binding Path=Layouts,ElementName=Root}"
SelectedItem="{Binding Path=SelectedLocation.Layout, ElementName=Root}"/>
</WrapPanel>
</UserControl>
today I getting crazy while trying to do, what I think, is simple thing.
I want to be able to create my usercontrol, and use it in my column template in my datagrid
I have searched and tried several combinations, and nothing appear to work
Can anyone help me?
public class User
{
public string Name { get; set; }
public bool IsValid { get; set; }
}
partial class MyControl : UserControl
{
private string _value;
public string Value
{
get { return _value; }
set { _value = value;
txt.Text = value;
}
}
public MyControl()
{
InitializeComponent();
}
}
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock x:Name="txt" Text="[undefined]"></TextBlock>
</Grid>
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
var items = new List<User>();
items.Add(new User{Name = "user 1", IsValid = true});
items.Add(new User { Name = "user 2", IsValid = false });
myGrid.ItemsSource = items;
}
}
<Grid x:Name="LayoutRoot" Background="White">
<sdk:DataGrid x:Name="myGrid" AutoGenerateColumns="False" IsReadOnly="True">
<sdk:DataGrid.Columns>
<sdk:DataGridTemplateColumn Header="Name">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<SilverlightApplication1:MyControl Value="{Binding Name}"></SilverlightApplication1:MyControl>
<!--<TextBlock Text="{Binding Name}"></TextBlock>-->
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</Grid>
Edited:
I also tried the following, but I get no results on my grid:
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock x:Name="txt" Text="{Binding Value}"></TextBlock>
</Grid>
public partial class MyControl : UserControl
{
public DependencyProperty ValueProperty =
DependencyProperty.Register("Value",
typeof(string),
typeof(MyControl),
new PropertyMetadata(OnValueChanged));
public string Value
{
get
{
return (string)GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
NotifyPropertyChanged("Value");
}
}
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyControl) d).Value = (String)e.NewValue; //ERROR: here I got always empty string
}
public MyControl()
{
InitializeComponent();
}
}
The reason why your first code didn't work is simple. To be able to bind the "Value" property on your "MyControl" (Value={Binding Name}), it has to be a Dependency Property. which you fixed in your second bit of code.
Here's what I did (and that worked well):
<UserControl x:Class="BusinessApplication8_SOF_Sandbox.Controls.MyControl"
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"
d:DesignHeight="300" d:DesignWidth="400" Name="myControl">
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Name="textBlock" Text="{Binding Value, ElementName=myControl}"/>
</Grid>
</UserControl>
For the rest, I used your code.
Another possibility, which should be OK in case you only want the data to flow in one direction ("One Way" from source to target), as it is the case when using the TextBlock control is to update the Text property in the "OnValueChanged". here's the code for the Value property:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(MyControl),
new PropertyMetadata("", OnValueChanged));
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = (MyControl)d;
var oldValue = (string)e.OldValue;
var newValue = target.Value;
target.OnValueChanged(oldValue, newValue);
}
protected virtual void OnValueChanged(string oldValue, string newValue)
{
textBlock.Text = newValue;
}
and you can remove the binding in xaml:
<TextBlock Name="textBlock" />
this worked for me as well.
Hope this helps ;)
you need to implement the INotifyPropertyChanged interface in your User class so that bound user controls are aware if any of the bound properties change. See the following page with the details how to implement it : http://www.silverlightshow.net/tips/How-to-implement-INotifyPropertyChanged-interface.aspx
As you can see you need to implement the interface and in the setters raise the event OnPropertyChanged
Then it should work with your bindings.
Best,
Tim