I want to bind a button's width to some textbox's text value, although I want to always have a button width's that's twice what is written on the textbox. This is:
textBox1.Text = 10
will set
button1.Width = 20
Can I only do this through ValueConverters or is there other way to do it?
Thanks
Using IValueConverter is the easy solution but if you do not wish to do so, then you can try binding textbox1 and button1 with a single variable. For example, let say you have created two controls as seen in below and have binded into a single variable called ButtonText. For simplicity, Content of the button will be modified instead of Width of the button.
In xaml:
<TextBox Text="{Binding ButtonText, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="{Binding ButtonText, Mode=OneWay}"/>
In ViewModel:
public string ButtonText
{
get { return _buttonText; }
set
{
int result;
if (int.TryParse(value, out result))
_buttonText = (result * 2).ToString();
else
_buttonText = value;
OnPropertyChanged("ButtonText");
}
}
private string _buttonText;
Unfortunately, this solution does not work in .NET 4.0 because the way .NET 4.0 handles OneWayToSource, as stated in this article. Basically, the issue is that the Textbox will be updated with the value from ButtonText after it is set by the Textbox although its Mode was configured as "OneWayToSource". This solution will work for .NET 3.5.
To get around this OneWayToSource issue in .NET 4.0, you can use BlockingConverter (type of IValueConverter) to separate each time that the resource is used and set x:Shared="False", as stated in this article. Then again, you are using the IValueConverter but at least you are not using it to modify the value.
Bindings that are not simple assignments, that is what value converters are for.
(No other way to do it.)
Related
I'm fairly new to WPF. I have the following radio button in my application
<Viewbox Height="30">
<RadioButton Content="B1" GroupName="InputBelt" IsChecked="{Binding RBChecked, Mode=TwoWay, FallbackValue=True}" VerticalAlignment="Center"/>
</Viewbox>
<Viewbox Height="30">
<RadioButton Content="B2" GroupName="InputBelt" IsChecked="{Binding RBChecked, Converter={StaticResource boolconverter}, Mode=TwoWay}" VerticalAlignment="Center"/>
</Viewbox>
I have defined datacontext in xaml file
<Window.DataContext>
<vm:TestViewModel />
</Window.DataContext>
The issue is when the page is loaded for the 1st time, everything is fine. But when I go to some other page in the application and comes back to this page, the application crashes due to stackoverflow exception.
I even tried adding datacontext locally in radiobutton tag but it isn't working either.
Property structure given below.
private bool _bRBChecked;
public bool RBChecked
{
get { return _bRBChecked; }
set
{
_bRBChecked = value;
RaisePropertyChanged("RBChecked");
}
}
Upon investigating further, I found out that the RaisePropertyChanged of the binded property is being called too many times. This issue occurs only with the property binded to radio button. I have other controls which has two-way bind with other properties in the same page and it seems to work fine.
Now I have tried the below fix suggested in another stackoverflow question and it seems to be working.
set
{
if (_bRBChecked != value)
{
_bRBChecked = value;
RaisePropertyChanged("RBChecked");
}
}
But I would like to know the root cause of this issue and why the property is being set so many times and find any alternate fix if possible. Please let me know if I am missing anything.
Any insight is highly appreciable.
Your change notification is not protected from recursion. Property A changing Property B, whose change changes Property A...
A simple solution is this:
set
{
if(value != _bRBChecked){
_bRBChecked = value;
RaisePropertyChanged("RBChecked");
}
}
Simply check if the value is actually a change, before you go tell everyone about it. This pattern is explicitly used in the Examples. I am 90% sure the Depdency Properties have a similar recursion protection, but it would not be the first time I was wrong.
I think it is fairly easy to figure this out, based on the fix you shared.
What happens in steps:
You set the new value in one of the radio buttons
The event is raised
Since it's two way binding, the binding of the second radio button sets the value again to the other radio button
The event is raised again due to 3
Go back to 1 as now the value is set again for the first radio button.
With your fix the value is not set (the setter it's not called) so the event is not triggered again.
Problem:
When I change the value of "LuxVoltage" in the GUI via the Slider or the NumericUpDown the Value jumpes from "default value" (in this case 0) to the "actual value". Assuming I set the Value to 1000 and print out every set this is what the output looks like (it kind of "flickers"):
Output:
0
1000
0
1000
[repeat]
XAML: (Using MahApps.Metro "NumericUpDown")
<metro:NumericUpDown
Value="{Binding LuxVoltage, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Minimum="0"
Maximum="65535"
/>
<Slider
Value="{Binding LuxVoltage, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Minimum="0"
Maximum="65535"
/>
C#: (Using Prisms "BindableBase")
private ushort _luxVoltage = 0;
public ushort LuxVoltage
{
get { return _luxVoltage; }
set { SetProperty(ref _luxVoltage, value); }
}
Requirement:
I need to manipulate the same value from two controls. The "slider" to simply change the value fast, and the "NumericUpDown" tho provide precision
I don't know why you have a problem with flickering, but when I've had two GUI things chained from one value in the past I've always gone for the approach (using your case) of binding the NumericUpDown to the property in your viewmodel, and then binding the Slider to the NumericUpDown property. It might work for you.
The type of the Value of Slider is double. As you are binding to a ushort this causes multiple updates even though the slider does not move. Which then might cause additional changes fired from the other control, try adding this to the Slider binding so it when you drag the slider it will only increment it by the tick frequency which is defaulted to "1.0".
IsSnapToTickEnabled="True"
seems like that the actual problem lives somewhere else in my codebase.
- Binding from 2 Controls to one Property schould just work fine.
- Or consider binding one of the Controls to the value of the other, whose value is bound to the Property*
Try checking the incoming value on the setter for a meaningful change before you call SetProperty.
private ushort _luxVoltage = 0;
public ushort LuxVoltage
{
get { return _luxVoltage; }
set
{
if (_luxVoltage != value)
{
SetProperty(ref _luxVoltage, value);
}
}
}
In my MVVM test project I want to bind my textbox to the object from viewmodel:
public class ContactViewModel : BaseNotifyPropertyChanged
{
Contact _selectedItem;
public ContactViewModel()
{
ContactModel contactModel = new ContactModel();
_selectedItem = contactModel.ContactList[1]; // this contains first contact from the list;
}
}
public Contact SelectedContact
{
get
{
return _selectedItem;
}
}
in my Contact class I am overriding ToString Method in order to show first Contact's first name:
public override string ToString()
{
return _firstName.ToString();
}
And here is my XAML binding:
<TextBox Height="23" HorizontalAlignment="Left" Name="SelectedItemTextBox" Text="{Binding Path=SelectedContact}" VerticalAlignment="Top" Width="120" />
And for some reason this textbox is always empty. However, if I change
public String SelectedContact
{
get
{
return _selectedItem.LastName;
}
}
it works perfectly.
Stanislav, you did a mistake in other place. You try to bind to object, binding doesn't know what to show and apply ToString() to your Contact object. If you overrode ToString(), it had to show a returned value of this method. I created the test app, and it works in this way!
What I can see in your code, in ToString() you return FirstName, but in changed SelectedContact it is SecondName - did you fill first name before?
You wrote in comment that tried to access to first element, but in code you take second element of ContactList
Moreover, use binding in this way is incorrect. If you want to access to LastName use next way:
<TextBox Text="{Binding Path=SelectedContact.LastName, Mode=OneTime}" />
And remove ToString() overriding.
EDIT: Unlike to other controls where binding is OneWay by default in TextBox it is TwoWay by default. It was done because native behavior of TextBox is show and edit value (not only show as in other controls). Moreover if you don't plan to change value (you don't plan, because ContactModel doesn't implement INotifyPropertyChanged) it is recommended to use OneTime mode (for performance).
TwoWay has some restriction - you can't use it for read-only property (SelectedContact is read-only in your code). Because binding can't change the value in this case - make sense. It is strange that app lunched in your case and TextBox was empty, because in my case I get the error "A TwoWay or OneWayToSource binding cannot work on the read-only property 'SelectedContact' of type 'WpfApplication1.ContactViewModel'." until I changed binding mode in TextBox.
I guess you followed this Article on MSDN:
http://msdn.microsoft.com/en-us/library/ms742521%28v=vs.110%29.aspx
Alltough the article says, that the standard representation of a ListBox is a List of ToString representation of its contents, this is not the case for every other element.
I would highly recommend to create a DataBinding Template for you Contact class, it's a much cleaner way to implement this behaviour than overriding ToString.
Ah, found it, I just had to change my TextBox to the TextBlock and now everything works properly !
It seems like TextBlock does understand how to show objects, but TextBox doesn't.
I'm new to both Caliburn and WPF, so excuse me if it is a rather trivial question.
The scenario is the following:
I have multiple controls (like buttons and textboxes - the latter is the important part).
Their state (Enabled/Disabled) are dependent on a boolean property.
The first suggested method I tried was using the Can[FunctionName] convention and NotifyOfPropertyChange(() => Can[FunctionName]). It worked well with the button, but it did not work with the textbox.
How do I bind IsEnabled property to a state without using the code-behind of the View?
The code I tried in the ViewModel didn't work for the textbox:
private bool _buttonEnableState = true;
public bool ButtonEnableState
{
get
{
return _buttonEnableState;
}
set
{
_buttonEnableState = value;
NotifyOfPropertyChange(() => CanTheButton);
NotifyOfPropertyChange(() => CanTheTextBox);
}
}
public bool CanTheButton
{
get
{
return ButtonEnableState;
}
}
public void TheButton()
{
}
public bool CanTheTextBox
{
get
{
return ButtonEnableState;
}
}
From the View:
<Button x:Name="TheButton" Content="This is the button" ... />
<TextBox x:Name="TheTextBox" ... />
Thanks in advance!
Have you tried the obvious?:
<Button Content="This is the button" IsEnabled="{Binding ButtonEnableState}" />
<TextBox x:Name="TheTextBox" IsEnabled="{Binding ButtonEnableState}" />
UPDATE >>>
So, continuing the conversation from the comments... now you have a public property in your AppViewModel class and an instance of that class is set as the DataContext of your view that contains the Button and TextBox controls?
Let's see if the Binding is really working or not... try changing your code to this:
<Button Content="{Binding ButtonEnableState}" />
If the Button.Content is set then the Binding works just fine and you have a different problem.
UPDATE 2 >>>
As #Charleh mentioned, you also need to make sure that you have notified the INotifyPropertyChanged interface of the change of property value:
NotifyOfPropertyChange(() => ButtonEnableState);
I don't think what I'm about to suggest is necessarily the correct way of doing things, but it may give you the result you're after.
In order to get the control to be disabled based on a Can<name> property, you need to confirm to the conventions that Caliburn uses, so in this case, supplying a function <name> should work:
public void TheTextBox()
{
}
As a result of the default conventions, I believe this will be called every time the KeyDown event is fired.
That said, you probably want to bind your text content to something, and you'll want to use the x:Name property convention to choose which property, that means you'll have to attach the TheTextBox() function in a different way, you should be able to do that using the Message.Attach property in the Caliburn namespace.
So your TextBox could look like this (where you've added the following namespace xmlns:cal="http://www.caliburnproject.org"):
<TextBox cal:Message.Attach="TheTextBox" Name="SomeTextProperty" />
Backing that up in your ViewModel, you'd have:
// Your Enabled Property (using your existing code).
public bool CanTheTextBox
{
get
{
return ButtonEnableState;
}
}
// Your dummy function
public void TheTextBox()
{
}
// Some text property (Just for demo, you'd probably want to have more complex logic in the get/set
public string SomeTextProperty
{
get; set;
}
You should then see the Enabled/Disabled behaviour, and be use the SomeTextProperty.
I'm not entirely sure I like this way of doing things, I just had a quick play to see if it worked. The following answer might be a cleaner solution, and establishes a new re-usable convention:
Adding a convention for IsEnabled to Caliburn.Micro
As a slight aside (not a direct answer), depending on how complicated your control/form is, you could investigate using multiple Views for the same ViewModel, in the past I've set up a ReadOnly and Editable view, and used a single property on the ViewModel to toggle between the two (essentially setting the entire state of the ViewModel). There are already default conventions so you can use multiple views with relative ease.
I have a databound TextBlock control (which is being used inside a DataTemplate to display items in a ListBox) and I want to make all the text in the control bold. I can't seem to find a property in the properties explorer to set the whole text to bold, and all I can find online is the use of the <Bold> tag inside the TextBlock, but I can't put that in as the data is coming directly from the data source.
There must be a way to do this - but how? I'm very inexperienced in WPF so I don't really know where to look.
Am I missing something, or do you just need to set the FontWeight property to "Bold"?
<TextBlock FontWeight="Bold" Text="{Binding Foo}" />
Rather than just having a TextBlock, try this:
<TextBlock>
<Bold>
<Run />
</Bold>
</TextBlock>
Then databind to the Run.TextProperty instead.
You say that the data is coming directly from the datasource; is it possible to place a layer of abstraction in front of it? Its quite common to create a View for what you are displaying, and have the View communicate with the data. The most common implementation of this idea is Model View View-Model (MVVM). Have a read about it online.
You might have a 'DisplayText' property that is bound to the textbox, and it is simply a 'getter' that wraps the underlying text. It can detect if the text is already wrapped in and if not, wrap it.
Eg.
public class TestView {
private Test datasource;
public TestView(Test source)
{
this.datasource = source;
}
public string DisplayText {
get {
if (datasource.Text.Contains("<bold>")==false) {
return "<bold>" + datasource.Text + "</bold>";
}
return datasource.Text;
}
}
}
Then, bind to the View instead of directly to the object.