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
Related
I'd like to bind the Width of my Columns to a Property in my Model so I can save it if the user resize it. I'd like a solution with no code behind.
This is what I have so far:
XAML:
<DataGrid x:Name="dgArticles" AutoGenerateColumns="False" ItemsSource="{Binding Specifications.Articles}" RowDetailsVisibilityMode="Visible">
<DataGrid.Columns>
<DataGridTextColumn x:Name="Number" Header="Number" Binding="{Binding Number}" Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.Specifications.Config.NumberColumnWidth}" MinWidth="70" >
Model:
public class Specifications
{
private ConfigurationGrid config
public ConfigurationGrid Config { get { return config; } set { } }
private ObservableCollection<Article> articles;
public ObservableCollection<Article> Articles
{
get { return articles; }
set { }
}
public class ConfigurationGrid : INotifyPropertyChanged
{
private double numberColumnWidth;
public double NumberColumnWidth
{
get { return numberColumnWidth; }
set { numberColumnWidth = value; OnPropertyChanged("numberColumnWidth"); }
}
public ConfigurationGrid() { }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this,
new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
I managed to bind the Width of a sub Datagrid Column which is in my RowDetailsTemplate to the Width of another column like so:
<DataGridTextColumn Header="Quantity" CellStyle="{StaticResource QuantityStyle}" Binding="{Binding Quantity, UpdateSourceTrigger=PropertyChanged, StringFormat=\{0:n\}}"
Width="{Binding Source={x:Reference Mesure}, Path=ActualWidth}"/>
This works fine but I don't know why it's not working on my main DataGrid.
After debugging I noticed that it doesn't even reach the Getter of NumberColumnWidth.
Does anyone know a way to make it work? Thank you
Edit
I tried the solution provided by #mm8 but it didn't work. It's still not reaching the Getter. Maybe I missed something. Here is what the code looks like right now:
XAML:
<UserControl x:Class="CachView.Views.GridView"
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:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:CachView.ViewModels"
xmlns:conv="clr-namespace:CachView.Converters"
xmlns:util="clr-namespace:CachView.Util"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<local:ArticleViewModel/>
</UserControl.DataContext>
<Grid Margin="10">
<DataGrid x:Name="dgArticles" AutoGenerateColumns="False" ItemsSource="{Binding Specifications.Articles}" RowDetailsVisibilityMode="Visible">
<DataGrid.Resources>
<util:BindingProxy x:Key="proxy" Data="{Binding}"/>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn x:Name="Number" Header="Number" Binding="{Binding Number}" Width="{Binding Data.Specifications.Config.NumberColumnWidth, Source={StaticResource proxy}}"
MinWidth="70">
</DataGridTextColumn>
Code behind:
public partial class GridView : UserControl
{
public GridView(ArticleViewModel a)
{
InitializeComponent();
this.DataContext = a;
}
}
And my BindingProxy class is the same as in the example:
class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
My project is a UserControl that is meant to be used from a WinForm application. Here is how it is implemented and how I set the DataContext and properties. It is done from the Controller of my WinForm application.
class Controller
{
private ArticleViewModel articleViewModel;
private ElementHost elementHost;
private MainWindow winformView;
public ArticleViewModel ArticleViewModel { get { return articleViewModel; } }
public Collection<Article> Articles { get; set; }
public Specifications Specs { get; set; }
public Controleur(MainWindow view) // The view is received from Program.cs
{
this.winformView = view;
Articles = new Collection<Article>();
populateArticles(); // This create hard coded articles for testing purpose
ConfigurationGrid config= new ConfigurationGrid();
config.NumberColumnWidth = 300;
Specs = new Specifications(Articles);
Specs.Config = config;
articleViewModel = new ArticleViewModel(Specs);
GridView gridView = new GridView(articleViewModel); //This is my WPF UserControl
elementHost = new ElementHost();
elementHost.Dock = DockStyle.Fill;
this.winformView.Controls.Add(elementHost);
elementHost.Child = gridView;
}
My ViewModel:
public class ArticleViewModel
{
private Specifications specifications;
public Specifications Specifications { get { return specifications; } set { } }
public ArticleViewModel() { }
public ArticleViewModel(Specifications c)
{
this.specifications = c;
}
}
Any help or suggestion is welcome.
A DataGridTextColumn is not a visual element that gets added to the element tree so you won't be able to bind to a RelativeSource since there are no ancestors to bind to.
If you want to be able to bind the Width property to a view model property you could use a BindingProxy object that captures the DataContext as suggested in the following blog post.
[WPF] How to bind to data when the DataContext is not inherited: https://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
I'l start by letting a picture do some talking.
So you see, I want to create a WPF user control that supports binding to a parent window's DataContext. The user control is simply a Button and a ListBox with a custom ItemTemplate to present things with a Label and a Remove Button.
The Add button should call an ICommand on the main view model to interact with the user in selecting a new thing (instance of IThing). The Remove buttons in the ListBoxItem in the user control should similarly call an ICommand on the main view model to request the related thing's removal. For that to work, the Remove button would have to send some identifying information to the view model about the thing requesting to be removed. So there are 2 types of Command that should be bindable to this control. Something like AddThingCommand() and RemoveThingCommand(IThing thing).
I got the functionality working using Click events, but that feels hacky, producing a bunch of code behind the XAML, and rubs against the rest of the pristine MVVM implementation. I really want to use Commands and MVVM normally.
There's enough code involved to get a basic demo working, I am holding off on posting the whole thing to reduce confusion. What is working that makes me feel like I'm so close is the DataTemplate for the ListBox binds the Label correctly, and when the parent window adds items to the collection, they show up.
<Label Content="{Binding Path=DisplayName}" />
While that displays the IThing correctly, the Remove button right next to it does nothing when I click it.
<Button Command="{Binding Path=RemoveItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
This isn't terribly unexpected since the specific item isn't provided, but the Add button doesn't have to specify anything, and it also fails to call the command.
<Button Command="{Binding Path=AddItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
So what I need is the "basic" fix for the Add button, so that it calls the parent window's command to add a thing, and the more complex fix for the Remove button, so that it also calls the parent command but also passes along its bound thing.
Many thanks for any insights,
This is trivial, and made so by treating your UserControl like what it is--a control (that just happens to be made up from other controls). What does that mean? It means you should place DependencyProperties on your UC to which your ViewModel can bind, like any other control. Buttons expose a Command property, TextBoxes expose a Text property, etc. You need to expose, on the surface of your UserControl, everything you need for it to do its job.
Let's take a trivial (thrown together in under two minutes) example. I'll leave out the ICommand implementation.
First, our Window
<Window x:Class="UCsAndICommands.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:t="clr-namespace:UCsAndICommands"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<t:ViewModel />
</Window.DataContext>
<t:ItemsEditor Items="{Binding Items}"
AddItem="{Binding AddItem}"
RemoveItem="{Binding RemoveItem}" />
</Window>
Notice we have our Items editor, which exposes properties for everything it needs--the list of items it is editing, a command to add a new item, and a command to remove an item.
Next, the UserControl
<UserControl x:Class="UCsAndICommands.ItemsEditor"
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:t="clr-namespace:UCsAndICommands"
x:Name="root">
<UserControl.Resources>
<DataTemplate DataType="{x:Type t:Item}">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RemoveItem, ElementName=root}"
CommandParameter="{Binding}">Remove</Button>
<TextBox Text="{Binding Name}" Width="100"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<StackPanel>
<Button Command="{Binding AddItem, ElementName=root}">Add</Button>
<ItemsControl ItemsSource="{Binding Items, ElementName=root}" />
</StackPanel>
</UserControl>
We bind our controls to the DPs defined on the surface of the UC. Please, don't do any nonsense like DataContext=this; as this anti-pattern breaks more complex UC implementations.
Here's the definitions of these properties on the UC
public partial class ItemsEditor : UserControl
{
#region Items
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register(
"Items",
typeof(IEnumerable<Item>),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public IEnumerable<Item> Items
{
get { return (IEnumerable<Item>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
#endregion
#region AddItem
public static readonly DependencyProperty AddItemProperty =
DependencyProperty.Register(
"AddItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand AddItem
{
get { return (ICommand)GetValue(AddItemProperty); }
set { SetValue(AddItemProperty, value); }
}
#endregion
#region RemoveItem
public static readonly DependencyProperty RemoveItemProperty =
DependencyProperty.Register(
"RemoveItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand RemoveItem
{
get { return (ICommand)GetValue(RemoveItemProperty); }
set { SetValue(RemoveItemProperty, value); }
}
#endregion
public ItemsEditor()
{
InitializeComponent();
}
}
Just DPs on the surface of the UC. No biggie. And our ViewModel is similarly simple
public class ViewModel
{
public ObservableCollection<Item> Items { get; private set; }
public ICommand AddItem { get; private set; }
public ICommand RemoveItem { get; private set; }
public ViewModel()
{
Items = new ObservableCollection<Item>();
AddItem = new DelegatedCommand<object>(
o => true, o => Items.Add(new Item()));
RemoveItem = new DelegatedCommand<Item>(
i => true, i => Items.Remove(i));
}
}
You are editing three different collections, so you may want to expose more ICommands to make it clear which you are adding/removing. Or you could cheap out and use the CommandParameter to figure it out.
Refer the below code.
UserControl.XAML
<Grid>
<ListBox ItemsSource="{Binding Things}" x:Name="lst">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ThingName}" Margin="3"/>
<Button Content="Remove" Margin="3" Command="{Binding ElementName=lst, Path=DataContext.RemoveCommand}" CommandParameter="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Window.Xaml
<Window x:Class="MultiBind_Learning.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MultiBind_Learning"
Title="Window1" Height="300" Width="300">
<StackPanel Orientation="Horizontal">
<Button Content="Add" Width="50" Height="25" Command="{Binding AddCommnd }"/>
<local:UserControl2/>
</StackPanel>
Window.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new ThingViewModel();
}
}
ThingViewModel.cs
class ThingViewModel
{
private ObservableCollection<Thing> things = new ObservableCollection<Thing>();
public ObservableCollection<Thing> Things
{
get { return things; }
set { things = value; }
}
public ICommand AddCommnd { get; set; }
public ICommand RemoveCommand { get; set; }
public ThingViewModel()
{
for (int i = 0; i < 10; i++)
{
things.Add(new Thing() { ThingName="Thing" +i});
}
AddCommnd = new BaseCommand(Add);
RemoveCommand = new BaseCommand(Remove);
}
void Add(object obj)
{
things.Add(new Thing() {ThingName="Added New" });
}
void Remove(object obj)
{
things.Remove((Thing)obj);
}
}
Thing.cs
class Thing :INotifyPropertyChanged
{
private string thingName;
public string ThingName
{
get { return thingName; }
set { thingName = value; OnPropertyChanged("ThingName"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
BaseCommand.cs
public class BaseCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _method;
public event EventHandler CanExecuteChanged;
public BaseCommand(Action<object> method)
{
_method = method;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_method.Invoke(parameter);
}
}
Instead of Base command you can try RelayCommand from MVVMLight or DelegateCommand from PRISM libraries.
By default, your user control will inherit the DataContext of its container. So the ViewModel class that your window uses can be bound to directly by the user control, using the Binding notation in XAML. There's no need to specify DependentProperties or RoutedEvents, just bind to the command properties as normal.
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 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.
Can you bind to a local variable like this?
SystemDataBase.cs
namespace WebWalker
{
public partial class SystemDataBase : Window
{
private string text = "testing";
...
SystemDataBase.xaml
<TextBox
Name="stbSQLConnectionString"
Text="{SystemDataBase.text}">
</TextBox>
??
Text is set to the local variable "text"
The pattern is:
public string Text {get;set;}
and the binding is
{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}
If you want the binding to update automatically you should make it a DependencyProperty.
I think 3.5 added ElementName to bindings, so the following is a little easier:
<Window x:Name="Derp" ...
<TextBlock Text="{Binding Text, ElementName=Derp}"/>
To bind to a local "variable" the variable should be:
A property, not a field.
Public.
Either a notifying property (suitable for model classes) or a dependency property (sutable for view classes)
Notifying property example:
public MyClass : INotifyPropertyChanged
{
private void PropertyType myField;
public PropertyType MyProperty
{
get
{
return this.myField;
}
set
{
if (value != this.myField)
{
this.myField = value;
NotifyPropertyChanged("MyProperty");
}
}
}
protected void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Dependency property example:
public MyClass : DependencyObject
{
public PropertyType MyProperty
{
get
{
return (PropertyType)GetValue("MyProperty");
}
set
{
SetValue("MyProperty", value);
}
}
// Look up DependencyProperty in MSDN for details
public static DependencyProperty MyPropertyProperty = DependencyProperty.Register( ... );
}
If you're doing a lot of this, you could consider binding the DataContext of the whole window to your class. This will be inherited by default, but can still be overridden as usual
<Window DataContext="{Binding RelativeSource={RelativeSource Self}}">
Then for an individual components you can use
Text="{Binding Text}"
To bind a local variable which is present in your Window class it has to be :
1. Public property
2. A notifying property. For this your window class should implement INotifyPropertyChanged interface for this property.
Then in the constructor
public Assgn5()
{
InitializeComponent();
this.DataContext = this; // or **stbSQLConnectionString**.DataContext = this;
}
<TextBox
Name="stbSQLConnectionString"
Text="{Binding text}">
</TextBox>
Need to add the following line in the int constructor:
this.DataContext = this;
And use this in the XAML:
<TextBox Text = "{Binding SomeProperty}" />
Exmaple:
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public string PersonName { get; set; }
public MainWindow()
{
InitializeComponent();
PersonName = "default";
this.DataContext = this;
}
void button1_Click(object sender, RoutedEventArgs args)
{
MessageBox.Show($"Hello {PersonName}");
}
}
MainWindow.xaml
<StackPanel>
<TextBox Name="textbox1"
Text="{Binding PersonName, Mode=TwoWay}"
/>
<Button Name="button1" Click="button1_Click" Content="Click Me" />
</StackPanel>