I have following code:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<TextBox Text="{Binding Path=Name,
Mode=OneWayToSource,
UpdateSourceTrigger=Explicit,
FallbackValue=default text}"
KeyUp="TextBox_KeyUp"
x:Name="textBox1"/>
</Grid>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void TextBox_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
BindingExpression exp = this.textBox1.GetBindingExpression(TextBox.TextProperty);
exp.UpdateSource();
}
}
}
public class ViewModel
{
public string Name
{
set
{
Debug.WriteLine("setting name: " + value);
}
}
}
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Window1 window = new Window1();
window.DataContext = new ViewModel();
window.Show();
}
}
I want to update source only when "Enter" key is pressed in textbox. This works fine. However binding updates source at program startup. How can I avoid this? Am I missing something?
The problem is, that DataBinding is resolved on the call of Show (and on InitializeComponent, but that is not important for you, because at that point your DataContext is not set yet). I don't think you can prevent that, but I have an idea for a workaround:
Do not set the DataContext before you call Show(). You can achieve this (for example) like this:
public partial class Window1 : Window
{
public Window1(object dataContext)
{
InitializeComponent();
this.Loaded += (sender, e) =>
{
DataContext = dataContext;
};
}
}
and:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Window1 window = new Window1(new ViewModel());
window.Show();
}
Change your Binding Mode to Default
<TextBox Text="{Binding Path=Name,
Mode=Default,
UpdateSourceTrigger=Explicit,
FallbackValue=default text}"
KeyUp="TextBox_KeyUp"
x:Name="textBox1"/>
Related
I have items stored in the ItemSource property of a combobox, however, when the user types in a name that does not exist in the list I need it to create a new object and use that as the SelectedObject. I am pretty new to WPF and used to WindowsForms, so I might just be going about doing this the totally wrong way, any input is appreciated.
Xaml:
<Window x:Class="ComboExample.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">
<StackPanel>
<ComboBox x:Name="cboName" IsEditable="True" SelectionChanged="ComboBox_SelectionChanged"></ComboBox>
<DockPanel>
<Label>Selected Value</Label>
<TextBox Text="{Binding Name}"></TextBox>
</DockPanel>
<Button Click="Button_Click">Click Me</Button>
</StackPanel>
and code behind (which displays "value is null" if you type a new value in
class SomeClass
{
public SomeClass(string name) {this.Name = name;}
public string Name { get; set; }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
cboName.ItemsSource = new SomeClass[] { new SomeClass("A"), new SomeClass("B") };
cboName.DisplayMemberPath = "Name";
cboName.SelectedItem = cboName.ItemsSource.OfType<SomeClass>().FirstOrDefault();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SomeClass value = cboName.SelectedValue as SomeClass;
if (value == null)
MessageBox.Show("No item is selected.");
else
MessageBox.Show("An item is selected.");
}
SomeClass empty = new SomeClass("");
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DataContext = cboName.SelectedItem as SomeClass;
if (DataContext == null)
cboName.SelectedValue = DataContext = empty;
}
}
here one way to do it:
<StackPanel>
<ComboBox x:Name="cboName" ItemsSource="{Binding ComboBoxItems}" DisplayMemberPath="Name" SelectedItem="{Binding ComboBoxItems[0],Mode=OneTime}" IsEditable="True"/>
<DockPanel>
<Label>Selected Value</Label>
<TextBox Text="{Binding SelectedItem.Name,ElementName=cboName}"></TextBox>
</DockPanel>
<Button Click="Button_Click">Click Me</Button>
</StackPanel>
and the code behind :
public partial class MainWindow : Window
{
private ObservableCollection<SomeClass> _comboBoxItems;
public ObservableCollection<SomeClass> ComboBoxItems
{
get
{
return _comboBoxItems;
}
set
{
_comboBoxItems = value;
}
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
ComboBoxItems = new ObservableCollection<SomeClass>()
{
new SomeClass("First Name"),
new SomeClass("Second Name"),
new SomeClass("Third Name")
};
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (!ComboBoxItems.Any(x => x.Name == cboName.Text))
{
ComboBoxItems.Add(new SomeClass(cboName.Text));
}
}
}
public class SomeClass
{
public SomeClass(string name) { this.Name = name; }
public string Name { get; set; }
}
To better manage your objects you may consider
defining a new property For the Selected ComboBox Item (Of Type SomeClass), and bind it to the ComboBox SelectedItem,
Use ObservableCollection instead of just list and Implement the INotifyPropertyChanges Interface.
I'm building a Windows Phone 8 app. I have a UserControl whose contents should get updated asynchronously. My model implements INotifyPropertyChanged. When I update a value in my model it propagates to a TextBox control as it should but not to the contents of my UserControl.
What part of the puzzle am I missing or is it just not possible?
Here's my reproduction scenario.
App page:
<phone:PhoneApplicationPage x:Class="BindingTest.MainPage">
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Button Click="Button_Click" Content="Click" HorizontalAlignment="Left" Margin="147,32,0,0" VerticalAlignment="Top"/>
<TextBlock HorizontalAlignment="Left" Margin="69,219,0,0" TextWrapping="Wrap" Text="{Binding Bar}" VerticalAlignment="Top" Height="69" Width="270"/>
<app:MyControl x:Name="Snafu" HorizontalAlignment="Left" Margin="69,319,0,0" Title="{Binding Bar}" VerticalAlignment="Top" Width="289"/>
</Grid>
</phone:PhoneApplicationPage>
This is the code behind with the model class (Foo)
public partial class MainPage : PhoneApplicationPage
{
Foo foo;
// Constructor
public MainPage()
{
InitializeComponent();
foo = new Foo();
ContentPanel.DataContext = foo;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
foo.Bar = "Gnorf";
}
}
public class Foo : INotifyPropertyChanged
{
string bar;
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string name)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public Foo()
{
Bar = "Welcome";
}
public string Bar
{
get
{
return bar;
}
set
{
bar = value;
OnPropertyChanged("Bar");
}
}
}
The UserControl xaml
<UserControl x:Class="BindingTest.MyControl">
<TextBox x:Name="LayoutRoot" Background="#FF9090C0"/>
</UserControl>
And the code behind for the UserControl
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
}
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(MyControl), new PropertyMetadata("", OnTitleChanged));
static void OnTitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyControl c = (MyControl)d;
c.Title = e.NewValue as String;
}
public string Title
{
get
{
return (string)GetValue(TitleProperty);
}
set
{
SetValue(TitleProperty, value);
LayoutRoot.Text = value;
}
}
}
When I run the example, the UserControl TextBox will contain welcome. When I click on the button the regular TextBox updates to Gnorf but the UserControl still displays Welcome.
I also discovered that if I only bind to the UserControl the PropertyChanged event handler is null when the call to set_DataContext returns. The DataBinding infrastructure seems to infer that the binding to my UserControl is a one-time binding instead of a regular one-way binding.
Any ideas?
Try This:-
<app:UserControl1 x:Name="Snafu" Title="{Binding Bar,Mode=TwoWay}" />
I checked It..This will work..:)
I can bind a combobox in the codebehind like this:
private void comboBox1_Loaded(object sender, RoutedEventArgs e)
{
var combo = sender as ComboBox;
App.SchedulerVM = new ScheduleViewModel();
combo.DataContext = App.SchedulerVM;
combo.ItemsSource = App.SchedulerVM.Frequency;
}
This works - my combobox has the items from the Frequency List in the SchedulerVM object.
However, I don't want to do any of this in the codebehind. But the ways I've done this in WP7 before aren't working here. If I comment out the last line in the Loaded method above and try to set the ItemsSource in XAML, it doesn't work - nothing shows up:
<ComboBox Name="comboBox1" Loaded ="comboBox1_Loaded" ItemsSource="{Binding
Frequency}" />
This doesn't work either:
<ComboBox Name="comboBox1" Loaded ="comboBox1_Loaded" ItemsSource="{Binding
App.SchedulerVM.Frequency}" />
Nor this:
<ComboBox Name="comboBox1" Loaded ="comboBox1_Loaded" ItemsSource="{Binding
SchedulerVM.Frequency}" />
Ideally, the DataContext wouldn't have to be explicitly set in the codebehind for this control either, it would be inherited from the LayoutRoot, where I've set it in the codebehind. But that's step 2 of my troubleshooting here.
What am I doing wrong? '
Thanks!
Edit
The ScheduleViewModel looks like this:
namespace SchedulerUI.ViewModels
{
public class ScheduleViewModel : INotifyPropertyChanged
{
//private properties
private Schedule _thisSchedule;
//public properties
public Schedule ThisSchedule
{
get { return _thisSchedule; }
set
{
if (value != _thisSchedule)
{
NotifyPropertyChanged("ThisSchedule");
}
_thisSchedule = value;
}
}
public List<string> Frequency = new List<string>();
public string Test;
//constructors
public ScheduleViewModel()
{
Frequency.AddRange(new string[] { "Daily", "Weekly", "Monthly" });
Test = "This is only a test.";
}
//INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Here's the entire XAML:
<UserControl x:Class="SchedulerUI.MainPage"
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">
<Grid x:Name="LayoutRoot" Background="White" Loaded="LayoutRoot_Loaded">
<ComboBox Height="23" HorizontalAlignment="Left" Margin="34,41,0,0" Name="comboBox1" Loaded ="comboBox1_Loaded" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Frequency}" />
<TextBox BorderBrush="Black" HorizontalAlignment="Left" Margin="34,41,0,0" Width="100" Height="100" DataContext="LayoutRoot.DataContext" Text="{Binding Test}" />
</Grid>
</UserControl>
Here's the entire codebehind:
namespace SchedulerUI
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
App.SchedulerVM = new ScheduleViewModel();
comboBox1.DataContext = App.SchedulerVM;
List<string> testlist = App.SchedulerVM.Frequency;
string teststring = App.SchedulerVM.Test;
}
private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
{
//App.SchedulerVM = new ScheduleViewModel();
//var root = sender as Grid;
//if (root != null)
//{
// root.DataContext = App.SchedulerVM;
//}
}
private void comboBox1_Loaded(object sender, RoutedEventArgs e)
{
//var combo = sender as ComboBox;
//App.SchedulerVM = new ScheduleViewModel();
//combo.DataContext = App.SchedulerVM;
//combo.ItemsSource = App.SchedulerVM.Frequency;
}
}
}
You binding is not working, because:
when you set ItemsSource in XAML its get executed first and it tries to bind the wrong/empty DataContext
then the Loaded event is raised which will set the correct DataContext but your already existing binding won't be refreshed automatically.
If you have to set the DataContext in the codebehind do it in your views constructor:
public YourView()
{
InitializeComponent();
combo.DataContext = App.SchedulerVM;
}
Then the following binding should work:
<ComboBox Name="comboBox1" ItemsSource="{Binding Frequency}" />
The databinding in WPF/Silverlight needs public properties. Currently Frequency is a public field on your viewmodel change it to a property and everthing should work:
private List<string> frequency = new List<string>();
public List<string> Frequency { get { return frequency; } set { frequency = value; }
And that is why it worked your initial loaded event because you didn't used databind there but you just set the combo.ItemsSource.
I want to create a Style for a WPF ListBox that includes a Button in the ControlTemplate that the user can click on and it clears the ListBox selection.
I dont want to use codebehind so that this Style can be applied to any ListBox.
I have tried using EventTriggers and Storyboards and it has proved problematic as it only works first time and stopping the Storyboard sets the previous selection back.
I know I could use a user control but I want to know if it is possible to achieve this using only a Style.
It is not possible to achieve this using XAML and only the classes provided by the .NET framework. However you can still produce a reusable solution by defining a new command (call it ClearSelectionCommand) and a new attached property (call it ClearSelectionOnCommand).
Then you can incorporate those elements into your style.
Example:
public class SelectorBehavior
{
public static RoutedCommand
ClearSelectionCommand =
new RoutedCommand(
"ClearSelectionCommand",
typeof(SelectorBehavior));
public static bool GetClearSelectionOnCommand(DependencyObject obj)
{
return (bool)obj.GetValue(ClearSelectionOnCommandProperty);
}
public static void SetClearSelectionOnCommand(
DependencyObject obj,
bool value)
{
obj.SetValue(ClearSelectionOnCommandProperty, value);
}
public static readonly DependencyProperty ClearSelectionOnCommandProperty =
DependencyProperty.RegisterAttached(
"ClearSelectionOnCommand",
typeof(bool),
typeof(SelectorBehavior),
new UIPropertyMetadata(false, OnClearSelectionOnCommandChanged));
public static void OnClearSelectionOnCommandChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
Selector selector = d as Selector;
if (selector == null) return;
bool nv = (bool)e.NewValue, ov = (bool)e.OldValue;
if (nv == ov) return;
if (nv)
{
selector.CommandBindings.Add(
new CommandBinding(
ClearSelectionCommand,
ClearSelectionCommand_Executed,
ClearSelectionCommand_CanExecute));
}
else
{
var cmd = selector
.CommandBindings
.Cast<CommandBinding>()
.SingleOrDefault(x =>
x.Command == ClearSelectionCommand);
if (cmd != null)
selector.CommandBindings.Remove(cmd);
}
}
public static void ClearSelectionCommand_Executed(
object sender,
ExecutedRoutedEventArgs e)
{
Selector selector = (Selector)sender;
selector.SelectedIndex = -1;
}
public static void ClearSelectionCommand_CanExecute(
object sender,
CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
}
Example usage - XAML:
<Window x:Class="ClearSelectionBehaviorLibrary.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ClearSelectionBehaviorLibrary"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style x:Key="MyStyle" TargetType="Selector">
<Setter
Property="local:SelectorBehavior.ClearSelectionOnCommand"
Value="True"/>
</Style>
</Window.Resources>
<Grid>
<DockPanel>
<Button
DockPanel.Dock="Bottom"
Content="Clear"
Command="{x:Static local:SelectorBehavior.ClearSelectionCommand}"
CommandTarget="{Binding ElementName=TheListBox}"/>
<ListBox
Name="TheListBox"
ItemsSource="{Binding MyData}"
Style="{StaticResource MyStyle}"/>
</DockPanel>
</Grid>
</Window>
Example usage - Code Behind:
public partial class Window1 : Window
{
public List<string> MyData { get; set; }
public Window1()
{
MyData = new List<string>
{
"aa","bb","cc","dd","ee"
};
InitializeComponent();
DataContext = this;
}
}
To understand how the binding works, I implemented MyContainer derived from FrameworkElement. This container allowes to set Children and adds them into the logical tree. But the binding by ElementName does not work. What can I do with MyContainer to make it work, leaving the parent as FrameworkElement?
C#:
public class MyContainer : FrameworkElement
{
public MyContainer()
{
Children = new List<FrameworkElement>();
}
public List<FrameworkElement> Children { get; set; }
protected override IEnumerator LogicalChildren
{
get { return Children.GetEnumerator(); }
}
}
XAML:
<Window x:Class="WpfLogicalTree.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfLogicalTree"
Title="Window1" Height="300" Width="300">
<StackPanel>
<local:MyContainer>
<local:MyContainer.Children>
<TextBlock Text="Foo" x:Name="_source" />
<TextBlock Text="{Binding Path=Text, ElementName=_source}" x:Name="_target"/>
</local:MyContainer.Children>
</local:MyContainer>
<Button Click="Button_Click">Test</Button>
</StackPanel>
</Window>
Window1.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(_target.Text);
}
Instead of using LogicalChildren I had to call AddLogicalChild. So this works:
public class MyContainer : FrameworkElement
{
public MyContainer()
{
Children = new List<FrameworkElement>();
this.Loaded += new RoutedEventHandler(OnLoaded);
}
void OnLoaded(object sender, RoutedEventArgs e)
{
foreach (FrameworkElement fe in Children)
this.AddLogicalChild(fe);
}
public List<FrameworkElement> Children { get; set; }
}
AddLogicalChild sets the logical parent of the element, this is required to find NameScope where the "_source" name was registered. In our case the Name Scope is the Window1.
Note. AddLogicalChild will not result in LogicalChildren returning our children automatically, it only sets the Parent. So LogicalTreeHelper.GetChildren will be the empty collection. But we don't need it here.