XAML: Update binding when changing DataContext - c#

I have a simple XAML file, it contains a Label whose Foreground property contains a binding:
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="200" Height="100" >
<Label Content="Sampletext" Foreground="{Binding Path=Color}" />
</Grid>
When I load the template and apply a DataContext the Foreground still has the default value.
Is it possible to get bound foreground value without rendering the Grid?
// Load template
string templatePath = "/WpfApplication1;component/Template.xaml";
Grid grid = Application.LoadComponent(new Uri(templatePath, UriKind.Relative)) as Grid;
// Set dataContext
grid.DataContext = new { Color = Brushes.Green };
// Foregound still has default value
var foreground = ((Label)grid.Children[0]).Foreground;
Project can be downloaded here: http://dl.dropbox.com/u/21096596/WpfApplication1.zip

try
lblName.GetBindingExpression(Label.ForegroundProperty).UpdateTarget();
before
var foreground = ((Label)grid.Children[0]).Foreground;

There are automatic DataContext change notifications, the binding will update if the necessary conditions are met. One of them is that the control is loaded (IsLoaded == true) which is not the case in your code. The control will only load if you add it to the your UI somewhere.
Example test code:
private void Button_Click(object sender, RoutedEventArgs e)
{
Grid grid = null;
Action action = () =>
{
var foreground = ((Label)grid.Children[0]).Foreground;
MessageBox.Show(foreground.ToString());
grid.DataContext = new { Color = Brushes.Green };
foreground = ((Label)grid.Children[0]).Foreground;
MessageBox.Show(foreground.ToString());
};
grid = Application.LoadComponent(new Uri("Stuff/GridOne.xaml", UriKind.Relative)) as Grid;
if (grid.IsLoaded)
{
action();
}
else
{
grid.Loaded += (s, _) => action();
}
// This adds the grid to some StackPanel, if you do not do something like this
// nothing will happen since the control will not be loaded and thus the event
// will not fire, etc.
ControlStack.Children.Add(grid);
}

Why do you need the onetime binding? remove that, and it should work.

Wrap your DataContext in an object, and implement INotifyPropertyChanged, then the binding will update when the property changes, and there's no need to update the binding manually:
public class MyDataContext : INotifyPropertyChanged
{
private Brush color;
public Brush Color
{
get { return color; }
set
{
color = value;
RaisePropertyChanged("Color");
}
}
//implementation of PropertyChanged and RaisePropertyChanged omitted
}
and then update it like so:
var dc = new MyDataContext();
grid.DataContext = dc;
dc.Color = Brushes.Green; //this will trigger the NotifyPropertyChanged and update the binding
//color should be changed now
var foreground = ((Label)grid.Children[0]).Foreground;
Hopefully this helps...

If you want a property of a control to be binding to a property of DataContext but want to change the datacontext in runtime, there is a much more simple way to do it.
Create a ContentControl, then use ContentControl.ContentTemplate
<ContentControl Content=something>
<ContentControl.ContentTemplate>
<DataTemplate>
<Label Foreground="{Binding Path=Color}" />
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
Change the Content of the ContentControl instead of changing DataContext of the Label.

Related

C# bin new Textblock object to Textblock control in XAML file

I want to show in my C#-WPF application a text containing links. The texts are static and known during compile time.
The following is doing want i want when working directly on the XAML file:
<TextBlock Name="TextBlockWithHyperlink">
Some text
<Hyperlink
NavigateUri="http://somesite.com"
RequestNavigate="Hyperlink_RequestNavigate">
some site
</Hyperlink>
some more text
</TextBlock>
Since using MVVM i want to bind the Textblock to a newly constructed Textblock object, through a dependency property. The XAML then looks like this:
<StackPanel Grid.Row="1" Margin="5 0 0 0">
<TextBlock Height="16" FontWeight="Bold" Text="Generic Text with link"/>
<TextBlock Text="{Binding Path=TextWithLink, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
In my ViewModel i place
private void someMethod(){
...
TextWithLink = CreateText();
...
}
private TextBlock(){
TextBlock tb = new TextBlock();
Run run1 = new Run("Text preceeding the hyperlink.");
Run run2 = new Run("Text following the hyperlink.");
Run run3 = new Run("Link Text.");
Hyperlink hyperl = new Hyperlink(run3);
hyperl.NavigateUri = new Uri("http://search.msn.com");
tb.Inlines.Add(run1);
tb.Inlines.Add(hyperl);
tb.Inlines.Add(run2);
return tb;
}
private TextBlock _textWithLink;
public TextBlock TextWithLink {
get => _textWithLink;
set{
_textWithLink = value;
OnPropertyChanged();
}
}
The dependency property setup is working i see a new TextBlock getting assigned to the XAML control, however there is no content shown, just the displayed text reads
System.Windows.Controls.TextBlock
rather than the content. I cannot get my head around what i have to change to show the desired mixed text. Happy for an help.
Instead of using a TextBlock instance in a view model, you should instead use a collection of Inline elements with a UI element that accept it as the source of a Binding.
Since the Inlines property of a TextBlock is not bindable, you may create a deribed TextBlock with a bindable property like this:
public class MyTextBlock : TextBlock
{
public static readonly DependencyProperty BindableInlinesProperty =
DependencyProperty.Register(
nameof(BindableInlines),
typeof(IEnumerable<Inline>),
typeof(MyTextBlock),
new PropertyMetadata(null, BindableInlinesPropertyChanged));
public IEnumerable<Inline> BindableInlines
{
get { return (IEnumerable<Inline>)GetValue(BindableInlinesProperty); }
set { SetValue(BindingGroupProperty, value); }
}
private static void BindableInlinesPropertyChanged(
DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var textblock = (MyTextBlock)o;
var inlines = (IEnumerable<Inline>)e.NewValue;
textblock.Inlines.Clear();
if (inlines != null)
{
textblock.Inlines.AddRange(inlines);
}
}
}
Now you may use it like
<local:MyTextBlock BindableInlines="{Binding SomeInlines}"/>
with a view model property like this:
public IEnumerable<Inline> SomeInlines { get; set; }
...
var link = new Hyperlink(new Run("Search"));
link.NavigateUri = new Uri("http://search.msn.com");
link.RequestNavigate += (s, e) => Process.Start(e.Uri.ToString());
SomeInlines = new List<Inline>
{
new Run("Some text "),
link,
new Run(" and more text")
};

Two-way-binding: editing passed value from XAML control in the model setter does not update control

This is for a Windows 10 Universal App.
XAML:
<RelativePanel Padding="4" Margin="4,12,0,0">
<TextBlock x:Name="Label" Text="Class Name" Margin="12,0,0,4"/>
<ListView x:Name="ClassTextBoxes"
ItemsSource="{Binding TextBoxList}"
SelectionMode="None" RelativePanel.Below="Label">
<ListView.ItemTemplate>
<DataTemplate >
<RelativePanel>
<TextBox x:Name="tbox"
PlaceholderText="{Binding PlaceHolder}"
Text="{Binding BoxText,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Padding="4" Width="200" MaxLength="25"/>
<TextBlock x:Name="errorLabel"
RelativePanel.Below="tbox"
Text="{Binding Error, Mode=TwoWay}"
Padding="0,0,0,4"
FontSize="10"
Foreground="Red"/>
<Button Content="Delete" Margin="12,0,0,0" RelativePanel.RightOf="tbox"/>
</RelativePanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</RelativePanel>
Model:
public class TextBoxStrings : BaseModel
{
private string _placeholder;
public string PlaceHolder
{
get { return _placeholder; }
set
{
if (_placeholder != value)
{
_placeholder = value;
NotifyPropertyChanged();
}
}
}
private string _boxText;
public string BoxText
{
get { return _boxText; }
set
{
if (_boxText != value)
{
_boxText = CheckBoxText(value);
NotifyPropertyChanged();
}
}
}
public string CheckBoxText(string val)
{
var r = new Regex("[^a-zA-Z0-9]+");
return r.Replace(val, "");
}
}
ViewModel:
private TrulyObservableCollection<TextBoxStrings> _textBoxList;
public TrulyObservableCollection<TextBoxStrings> TextBoxList
{
get { return _textBoxList; }
set
{
if (_textBoxList != value)
{
_textBoxList = value;
RaisePropertyChanged();
}
}
}
and I add new TextBoxString objects to my TextBoxList collection from within my view-model.
I want to make it that users can't type in certain characters (or rather, they get deleted whenever they
are typed in.
This works...in the model. Setting breakpoints and looking at the values, everything in the Model is working: value goes into the setter and gets changed, _boxText holds the new value that is set from CheckBoxText();
But the problem is, in my View, the textbox doesn't reflect changes to the underlying text that I make in the model.
So if I type in "abc*()" into "tbox", the value in the model will be "abc". The value of the textbox, however, will still be "abc*()".
I have a feeling it has something to do with the fact that I'm editing items that are inside of a collection and I don't have anything implemented to handle changing items within a collection. I was under the impression that using INotifyPropertyChanged and ObservableCollection<T> would take care of that for me.
Does anyone have any suggestions?
Thank you!
Edit: So, now I'm trying to use TrulyObservableCollection because I thought this was the problem, but it hasn't helped. Here it is: https://gist.github.com/itajaja/7507120
But the problem is, in my View, the textbox doesn't reflect changes to the underlying text that I make in the model.
As you've seen, the TextBox do reflect changes to your model. When you type in "abc*()" in the TextBox, the value in the model will be changed to "abc". The problem here is that the binding system in UWP is "intelligent". For TwoWay bindings, changes to the target will automatically propagate to the source and in this scenario, binding system assumes that the PropertyChanged event will fire for corresponding property in source and it ignores these events. So even you have RaisePropertyChanged or NotifyPropertyChanged in you source, the TextBox still won't update.
In WPF, we can call BindingExpression.UpdateTarget Method to force the update. But this method is not available in UWP.
As a workaround, you should be able to use TextBox.TextChanged event to check the input like following:
private void tbox_TextChanged(object sender, TextChangedEventArgs e)
{
var tb = sender as TextBox;
if (tb != null)
{
var originalText = tb.Text;
var r = new Regex("[^a-zA-Z0-9]+");
if (originalText != r.Replace(originalText, ""))
{
var index = (tb.SelectionStart - 1) < 0 ? 0 : (tb.SelectionStart - 1);
tb.Text = r.Replace(originalText, "");
tb.SelectionStart = index;
}
}
}
However it may break your MVVM model, you can use data validation to avoid this and here is a blog: Let’s Code! Handling validation in your Windows Store app (WinRT-XAML) you can refer to. And for my personal opinion, data validation is a better direction for this scenario.
if (_boxText != value)
{
_boxText = CheckBoxText(value);
NotifyPropertyChanged();
}
Try changing this to:
var tmp = CheckBoxText(value);
if (_boxText != tmp)
{
_boxText = tmp;
NotifyPropertyChanged();
}
I hope, in your XAML, the binding to property BoxText is two-way, right?
You should edit BoxText and then send checked value to UI. Just send value to CheckBoxText and already edited should be assigned to _boxText. And then you should send BoxText to UI by calling RaisePropertyChanged("BoxTest"). Please, see the following code snippet:
private string _boxText;
public string BoxText
{
get { return _boxText; }
set
{
if (_boxText != value)
{
_boxText=CheckBoxText(value);
RaisePropertyChanged("BoxText");
}
}
}
There is no difference where you use INotifyPropertyChanged for one property of for properties placed in collection. The complete example with collections and ListView can be seen here

Binding to TextBlock Width property in MVVM

I have a TextBlock control in my view, its Width depends on the Text property.
I'm looking for some way to bind the TextBlocks Width to a property in my model,which will work as follows:
The setting of the Width must be done automatically based on Text
In my button click I would like to retrieve the Width
I've tried the code below, but it keeps the Width as 0 if I don't explicitly set it in the constructor of the view model.Tried Mode=OneWayToSource and Mode=OneWay but it made no difference, any suggestions?
View:
<Grid>
<TextBlock Text="Some text" Width="{Binding TextWidth,Mode=OneWayToSource}" />
<Button Content="Show Width" Height="30" Width="90" Command="{Binding ShowTextWidth}" />
</Grid>
View model:
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private DelegateCommand<object> showTextWidth;
public DelegateCommand<object> ShowTextWidth
{
get { return showTextWidth; }
set { showTextWidth = value; }
}
private double textWidth;
public double TextWidth
{
get { return textWidth; }
set
{
textWidth = value;
OnPropertyChanged("TextWidth");
}
}
public ViewModel()
{
//If I explicitly specify the width it works:
//TextWidth = 100;
ShowTextWidth = new DelegateCommand<object>(ShowWidth);
}
private void ShowWidth(object parameter)
{
MessageBox.Show(TextWidth.ToString());
}
}
Ended up creating an attached behavior by Maleak which was inspired by Kent Boogaarts Pushing read-only GUI properties back into ViewModel, can't believe it's so complicated to push the value of ActualWidth into the view model!
Width is a DependencyProperty on TextBlock. In this case it's a Target for Binding and TextWidth is your source for Binding. OneWayToSource seems like the way to go, you are setting TextWidth to 100 in the ViewModel which does not propogate to Width on TextBlock because it's OneWayToSource yes correct, Width (Target) is then setting TextWidth (Source) to Double.NaN because of OneWayToSource and that's why you're seeing 0...
ActualWidth should work like sa_ddam213 said but also consider that your TextBlock doesn't grow in Width (ActualWidth) when the Text changes because it is spanning the total width of your Grid layout. Either put it in a ColumnDefinition with Width set to Auto or make your HorizontalAlignment Left to see the ActualWidth change when the Text changes.
I have made some changes to your source code. Consider using CommandParameter on your button? Check out the link...
WpfApplication10.zip
you dont really need to set the Width if you choose a layout panel wich can handle this for you.
eg. a columndefinition with width=auto grows with your text
To set the Width depending on its containing Text you could bind the Width-Property to the Text-Property and use a converter like so:
<TextBlock Width="{Binding RelativeSource={RelativeSource Self}, Path=Text, Converter={StaticResource TextToWidth}}"
Your converter could look like:
TextBlock tb = new TextBlock();
tb.Text = value.ToString();
tb.Measure(new Size(int.MaxValue, 20)); //20 if there shouldnt be a linebreak
return tb.DesiredSize.Width;

Conditional textboxes with slider binding

I have three texboxes and one slider which changes their Text properties. What i have to do is to bind slider's value property with Text textbox property but in a specific way. When one of textboxes are activated(gotfocused) i need slider to change its Text property. And only that one. I have binded it so far but when i move the slider all textboxes are updated.
Any ideas?
I was reading about converters, but i don't see how to implement it within my program.
http://forums.create.msdn.com/forums/t/95548.aspx here you have got code of my slider and textblock.
What about simply changing the active binding when a textbox receives focus:
Code Behind:
private Binding _activeBinding;
private TextBox _activeTextbox;
private TextBox ActiveTextBox
{
get { return _activeTextbox; }
set
{
// Check if a binding exists, initialize if one does not
if (_activeBinding == null)
{
_activeBinding = new Binding("Value");
_activeBinding.Source = this.sld;
}
if (_activeTextbox != null)
{
// Clear the binding
_activeTextbox.ClearValue(TextBox.TextProperty);
}
_activeTextbox = value;
if (_activeTextbox != null)
{
// Set the new binding
_activeTextbox.SetBinding(TextBox.TextProperty, _activeBinding);
}
}
}
private void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
this.ActiveTextBox = sender as TextBox;
}
XAML:
<Grid>
<StackPanel>
<TextBox GotFocus="TextBox_GotFocus">1</TextBox>
<TextBox GotFocus="TextBox_GotFocus">2</TextBox>
<TextBox GotFocus="TextBox_GotFocus">3</TextBox>
<Slider x:Name="sld"></Slider>
</StackPanel>
</Grid>

Suspend Databinding of Controls

I have a series of controls that are databound to values that change every second or so. From time to time, I need to "pause" the controls, so that they do not update their databindings (in either direction). I then later need to "unpause" the controls, so that they can update the datasource with their values, and receive future updates from the source as normal. How do I accomplish this?
Sample Binding:
<TextBox Text="{Binding UpdateSourceTrigger=LostFocus, Mode=TwoWay, Path=myData}">
You don't necessarily have to suspend binding. Another, and possibly simpler, way to do this is to suspend change notification in the view model. For instance:
private HashSet<string> _ChangedProperties = new HashSet<string>();
private void OnPropertyChanged(string propertyName)
{
if (_Suspended)
{
_ChangedProperties.Add(propertyName);
}
else
{
PropertyChangedEventHandler h = PropertyChanged;
if (h != null)
{
h(this, new PropertyChangedEventArgs(propertyName));
}
}
}
private bool _Suspended;
public bool Suspended
{
get { return _Suspended; }
set
{
if (_Suspended == value)
{
return;
}
_Suspended = value;
if (!_Suspended)
{
foreach (string propertyName in _ChangedProperties)
{
OnPropertyChanged(propertyName);
}
_ChangedProperties.Clear();
}
}
}
This will (if it's debugged and tested, which I haven't done) stop raising PropertyChanged events when Suspended is set to true, and when Suspended is set to false again it will raise the event for every property that changed while it was suspended.
This won't stop changes to bound controls from updating the view model. I submit to you that if you're letting the user edit properties on the screen at the same time that you're changing them in the background, there's something you need to take a closer look at, and it's not binding.
To deal with the source set the UpdateSourceTrigger to be Explicit.
<TextBox Name="myTextBox" Text="{Binding UpdateSourceTrigger=Explicit, Mode=TwoWay, Path=myData}">
Then in code behind reference a service which can deal with the actual updating as defined by your conditions.
BindingExpression be = myTextBox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
This will allow you to specify at which point the data goes back to the source from the target.
The target can be addressed by making a call to the same referenced service which has the knowledge on when to call the INotifyPropertyChanged.PropertyChanged event within your ViewModel.
class Data : INotifyPropertyChanged
{
Manager _manager;
public Data(Manager manager)
{
_manager = manager;
}
public event PropertyChangedEventHandler PropertyChanged;
String _info = "Top Secret";
public String Information
{
get { return _info; }
set
{
_info = value;
if (!_manager.Paused)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("Information"));
}
}
}
}
First of all you need create explicit binding:
Binding binding = new Binding("Content");
binding.Source = source;
binding.UpdateSourceTrigger = UpdateSourceTrigger.LostFocus;
binding.Mode = BindingMode.TwoWay;
txtContent.SetBinding(TextBox.TextProperty, binding);
Then when you need pause twoway binding you need destroy old binding and create new oneway binding with explicit trigger(in this case you binding source will not be updated when some property has been changed):
BindingOperations.ClearBinding(txtContent, TextBlock.TextProperty);
Binding binding = new Binding("Content");
binding.Source = source;
binding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;
binding.Mode = BindingMode.OneWay;
txtContent.SetBinding(TextBox.TextProperty, binding);
When you need to resume twoway binding you can explicit update source(if you need it) than destroy oneway binding and create twoway binding.
BindingExpression be = txtContent.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
BindingOperations.ClearBinding(txtContent, TextBlock.TextProperty);
Binding binding = new Binding("Content");
binding.Source = source;
binding.UpdateSourceTrigger = UpdateSourceTrigger.LostFocus;
binding.Mode = BindingMode.TwoWay;
txtContent.SetBinding(TextBox.TextProperty, binding);
If the control you want to suspend has a DataContext (ViewModel) you own, just save it off and null out the DataContext.
If the control has an inherited DataContext, setting that control's DataContext to null will block the inheritance. Then to resume binding updates, you use the ClearValue method to clear the DataContext DependencyProperty so inheritance kicks in again.
You can get fancy and use a VisualBrush to take a screen shot of the control you are suspending before clearing its DataContext, so the user doesn't see the control go blank.
My solution ended up as follows to prevent the text updating while the user is trying to change it.
XAML:
<TextBox Grid.Row="0" Grid.Column="1" TextAlignment="Right" VerticalAlignment="Center" Text="{Binding Path=MinimumValueInDisplayUnit, StringFormat=0.########}" MinWidth="100" Margin="4" GotFocus="TextBox_OnGotFocus" LostFocus="TextBox_OnLostFocus"/>
<TextBox Grid.Row="0" Grid.Column="2" TextAlignment="Right" VerticalAlignment="Center" Text="{Binding Path=MaximumValueInDisplayUnit, StringFormat=0.########}" MinWidth="100" Margin="4" GotFocus="TextBox_OnGotFocus" LostFocus="TextBox_OnLostFocus"/>
Code behind:
private void TextBox_OnGotFocus([CanBeNull] object sender, [CanBeNull] RoutedEventArgs e)
{
TextBox tb = sender as TextBox;
if (tb == null) return;
BindingExpression expression = tb.GetBindingExpression(TextBox.TextProperty);
if (expression == null) return;
// disable updates from source
BindingOperations.ClearBinding(tb, TextBlock.TextProperty);
tb.SetBinding(TextBox.TextProperty, new Binding(expression.ParentBinding.Path.Path) { Mode = BindingMode.OneWayToSource, UpdateSourceTrigger = UpdateSourceTrigger.Explicit , FallbackValue = tb.Text});
}
private void TextBox_OnLostFocus([CanBeNull] object sender, [CanBeNull] RoutedEventArgs e)
{
TextBox tb = sender as TextBox;
if (tb == null) return;
BindingExpression expression = tb.GetBindingExpression(TextBox.TextProperty);
if (expression == null) return;
// send current value to source
expression.UpdateSource();
// enable updates from source
BindingOperations.ClearBinding(tb, TextBlock.TextProperty);
tb.SetBinding(TextBox.TextProperty, new Binding(expression.ParentBinding.Path.Path) { Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.LostFocus });
}
Note that I assign the current Text as the fallback value of the OneWayToSource binding to have a start value (otherwise the text field would be empty once focused)
If you keep a reference to the view in the controller class you could fire an event from the view model when you want to suspend databinsing that has the controller clear the DataContext of the view. and when you are ready to start sending an receiving data again, reset the Views DataContext to the View Model.

Categories

Resources