How to clear a TextBox in MVVM? - wpf

I have a TextBox in a DataTemplate declared as follows:
<TextBox Grid.Row="1" Grid.Column="1" Margin="0,4,0,0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<cmd:EventToCommand Command="{Binding DataContext.NotesEnteredCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}">
<cmd:EventToCommand.CommandParameter>
<MultiBinding Converter="{StaticResource SimpleMultiValueConverter}">
<Binding Path="Row.OID" />
<Binding Path="Text" RelativeSource="{RelativeSource FindAncestor, AncestorType=TextBox}" />
</MultiBinding>
</cmd:EventToCommand.CommandParameter>
</cmd:EventToCommand>
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding DataContext.NotesEnteredCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}">
<KeyBinding.CommandParameter>
<MultiBinding Converter="{StaticResource SimpleMultiValueConverter}">
<Binding Path="Row.OID" />
<Binding Path="Text" RelativeSource="{RelativeSource FindAncestor, AncestorType=TextBox}" />
</MultiBinding>
</KeyBinding.CommandParameter>
</KeyBinding>
</TextBox.InputBindings>
What this TextBox basically does is execute a MVVM-Light RelayCommand when the Enter key is pressed or when losing focus.
My problem is that I cannot figure out a way in MVVM to clear the TextBox's Text value through XAML in the above two scenarios. It's very easy with in code-behind, but I can't figure it out in MVVM.
Any ideas?

If the text is part of your data layer and application logic, a string should exist in your Model or ViewModel and be cleared from there
For example,
<TextBox Text="{Binding NewNote}" ... />
and
void NotesEntered(int oid)
{
SaveNewNote(oid);
NewNote = string.Empty;
}
If it's part of the UI layer only, it should just be cleared with code-behind. It's perfectly acceptable to have UI-specific logic in the code-behind the UI, as that still maintains the separation of layers.
NewNoteTextBox_LostFocus(object sender, EventArgs e)
{
(sender as TextBox).Text = string.Empty;
}
NewNoteTextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Keys.Enter)
(sender as TextBox).Text = string.Empty;
}

You can use a UpdateSourceHelper. This really helped me out calling an event with no code-behind.
See here http://www.wiredprairie.us/blog/index.php/archives/1701
All you have to do is create a class "UpdateSourceHelper", connect it with your xaml like this
xmlns:local="using:WiredPrairie.Converter
and bind it to your TextBox (or whatever you want to bind to)...
<TextBox Height="Auto" Margin="0,6" Grid.Row="1" TextWrapping="Wrap" TabIndex="0"
Text="{Binding Value}"
local:UpdateSourceHelper.IsEnabled="True"
local:UpdateSourceHelper.UpdateSourceText="{Binding Value, Mode=TwoWay}"/>
If you want call LostFocus Event in the Helper, you simply have to add these 2 lines in your Helper:
tb.LostFocus += AttachedTextBoxLostFocus;
tb.LostFocus -= AttachedTextBoxLostFocus;
So it would look like this:
TextBox tb = (TextBox)obj;
if ((bool)args.NewValue)
{
tb.LostFocus += AttachedTextBoxLostFocus;
}
else
{
tb.LostFocus -= AttachedTextBoxLostFocus;
}
Right click on AttachedTextBoxLostFocus and generate the method. Now you can handle the Event like a code-behind event.

Related

How to use multibinding on a rectangle-command in mvvm?

I got the following rectangle
<Rectangle
Width="{Binding Width}"
Height="{Binding Length}"
Tag="{Binding Id}"
Name="rectangleDrawnMachine">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="MouseDown">
<cmd:EventToCommand
Command="{Binding Main.UpdateSelectedMachine, Mode=OneWay, Source={StaticResource Locator}}"
PassEventArgsToCommand="True"
CommandParameter="{Binding ElementName=rectangleDrawnMachine}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Rectangle>
The rectangle is bound to a model which is declared in an above ItemsControl. The document-structure is like the following:
<Grid>
<ItemsControl ItemsSource="{Binding AllMachines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Name="canvasDrawnMachines" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Rectangle Name="rectangleDrawnMachine"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Now my UpdateSelectedMachine-command needs at least three properties of the rectangle:
Position x
Position y
ID / Tag
With the CommandParameter of the rectangle itself my command will get a lot of informations about the rectangle (like the neccessary tag). But it doesnt get the neccessary information about the (X- & Y-)position of the canvas.
So my question is: how to use multibinding on my rectangle-command? And how to transfer the positions of the canvas?
You cannot pass multiple values by using command parameter.
In order to do so, you have to use multi binding.
<cmd:EventToCommand
Command="{Binding Main.UpdateSelectedMachine, Mode=OneWay, Source={StaticResource Locator}}"
PassEventArgsToCommand="True">
<cmd:EventToCommand.CommandParameter>
<MultiBinding Converter="{StaticResource YourConverter}">
<Binding Path="Canvas.Left" ElementName="canvasDrawnMachines"/>
<Binding Path="Canvas.Top" ElementName="canvasDrawnMachines"/>
<Binding Path="Tag" ElementName="canvasDrawnMachines"/>
</MultiBinding>
</cmd:EventToCommand.CommandParameter>
Your converter:
public class YourConverter : IMultiValueConverter
{
public object Convert(object[] values, ...)
{
return values.Clone();
}
}
Then, execution command logic:
public void OnExecute(object parameter)
{
var values = (object[])parameter;
var left = (double)values[0];
var top = (double)values[1];
var tag = values[2];
}
You can get the values of the Canvas.Left and Canvas.Top attached properties of the Rectangle that you are passing as the command parameter to the command like this:
double x = Canvas.GetLeft(rectangle);
double y = Canvas.GetTop(rectangle);
Do you know how to get the position in XAML-way?
Use a MultiBinding with a converter and bind to the Canvas.Left and Canvas.Top properties:
<MultiBinding Converter="{StaticResource converter}">
<Binding Path="(Canvas.Left)" ElementName="canvasDrawnMachines"/>
<Binding Path="(Canvas.Top)" ElementName="canvasDrawnMachines"/>
<Binding Path="Tag" ElementName="canvasDrawnMachines"/>
</MultiBinding>

Bind Multibinding Textbox in WPF MVVM

I have 3 TextBoxes bind with my class(Transaction) properties like this
<TextBox Text="{Binding Path=Transaction.Bills100,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="bills100" Grid.Column="2" Grid.Row="1" Margin="7"></TextBox>
<TextBox Text="{Binding Path=Transaction.Bill50,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="bills50" Grid.Column="2" Grid.Row="2" Margin="7"></TextBox>
<TextBox Text="{Binding Path=Transaction.Bill20,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="bills20" Grid.Column="2" Grid.Row="3" Margin="7"></TextBox>
Also I have another TextBox where I have done multibinding and done addition of the first three Textboxes like
<TextBox Grid.Column="2" IsReadOnly="True" Grid.Row="7" Grid.ColumnSpan="2" Margin="7" Name="TotalBills">
<TextBox.Text>
<MultiBinding Converter="{ikriv:MathConverter}" ConverterParameter="x+y+z" Mode="TwoWay">
<Binding Path="Text" ElementName="bills100" />
<Binding Path="Text" ElementName="bills50" />
<Binding Path="Text" ElementName="bills20" />
</MultiBinding>
</TextBox.Text>
</TextBox>
I want to bind this multibinding textbox with my class(Transaction) with property as Transaction.Total like my first three textboxes but it shows error
Property text is set more than once
Actually we cannot get the value of a two-way binding from one property and then set the value of another property.
Finally I came with a solution like this
In my Class Transaction
private double _totalBills;
public double TotalBills
{
get { return _totalBills; }
set { _totalBills= value; Notify("TotalBills"); }
}
In XAML(Instead of Multibinding)
<TextBox Text="{Binding Path=Transaction.TotalBills,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Grid.Column="2" IsReadOnly="True" Grid.Row="7" Grid.ColumnSpan="2" Margin="7" Name="TotalBills"/>
My ViewModel
public class MainViewModel: INotifyPropertyChanged
{
private Transaction _transactionDetails;
public MainViewModel()
{
Transaction= new Transaction();
_transactionDetails.PropertyChanged += _transactionDetails_PropertyChanged;
}
private void _transactionDetails_PropertyChanged(object sender,PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "TotalBills":
_calculate(); //My method for calculation
break;
}
}
}

Telerik RadTreeListView : Attaching event in rows

I'm using telerik radgridview in my WPF app
<telerik:RadTreeListView Grid.Row="1" Grid.ColumnSpan="2"
Name="WorkPreferenceTreeView"
AutoGenerateColumns="false"
IsReadOnly="True"
ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Views:WorkPreferenceSelectorView}}}"
RowIndicatorVisibility="Collapsed"
SelectedItem="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Views:WorkPreferenceSelectorView}}}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden">
<TelerikNavigation:RadContextMenu.ContextMenu>
<TelerikNavigation:RadContextMenu x:Name="RadContextMenu" />
</TelerikNavigation:RadContextMenu.ContextMenu>
<telerik:RadTreeListView.ChildTableDefinitions>
<telerik:TreeListViewTableDefinition ItemsSource="{Binding ItemPreferences}" />
</telerik:RadTreeListView.ChildTableDefinitions>
<telerik:RadTreeListView.Columns>
<telerik:GridViewDataColumn MinWidth="200" Width="*" CellTemplate="
{StaticResource ItemPreferenceskPreferenceCellTemplate}" Header="Preference" IsFilterable="false"
ShowFieldFilters="false" Name="A" />
And Im attaching an event on the control
private void InitializeControl()
{
WorkPreferenceTreeView.MouseDoubleClick += (WorkPreferenceTreeView_MouseDoubleClick);
}
Now my problem is the MouseDoubleClick event fires whenever I double click ANYWHERE in the radtreelistview (even in the Scroll Bar) where I just want it to fire when a selected item or row is double clicked. I'm wondering if I can just attach the MouseDoubleClick even in the selected item or each of the rows but I have no luck of finding way to do that. Any suggestion?
in your example, you did attached the event on the grid itself.
in fact you need to attach the event on the Row !
you can do it with RowStyle
(in this example, double click will toggle the IsExpanded row property)
<telerik:RadTreeListView ... >
<telerik:RadTreeListView.RowStyle>
<Style TargetType="telerik:TreeListViewRow">
<EventSetter
Event="MouseDoubleClick"
Handler="HandleRowDoubleClick" />
</Style>
</telerik:RadTreeListView.RowStyle>
</telerik:RadTreeListView>
and the code behind
public void HandleRowDoubleClick(object sender, RoutedEventArgs e)
{
var row = sender as Telerik.Windows.Controls.GridView.GridViewRow;
row.IsExpanded = !row.IsExpanded;
}
enjoy :)

displaying ComboBox.SelectedValue in TextBox

I am working on a WPF and have hit a serious wall. I have a data set that has two columns, ContactName and ContactTitle. I have successfully loaded all of the data into a ComboBox and even sorted it by ContactName. However, I am trying to now access that data and display part of it in a TextBox. (This is of course just a proof of concept type exercise, the final product will populate a variety of TextBoxes with the selected persons information). The problem is, I cannot get the info to populate in the TextBox. Here is the code that I have:
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
namespace MultiBindingInWPF_CS
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
//Create DataSet
CustomersDataSet customerDataSet = new CustomersDataSet();
//Create DataTableAdapter
CustomersDataSetTableAdapters.CustomersTableAdapter taCustomers = new CustomersDataSetTableAdapters.CustomersTableAdapter();
taCustomers.Fill(customerDataSet.Customers);
//Sort Data
SortDescription sd = new SortDescription("ContactName", ListSortDirection.Descending);
//Designate ItemSource
this.ComboBox1.ItemsSource = customerDataSet.Customers;
//Apply Sort
this.ComboBox1.Items.SortDescriptions.Add(sd);
}
private void ComboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
//Using SelectedIndex only to prove connection to TextBox is working
textBox1.Text = ComboBox1.SelectedIndex.ToString();
}
catch
{
textBox1.Text = "Invalid";
}
}
}
}
Then here is my XAML:
<Window x:Class="MultiBindingInWPF_CS.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
Title="Multibinding in WPF" Height="163" Width="300">
<Grid Loaded="Grid_Loaded">
<StackPanel Name="StackPanel1" Margin="12">
<Label Height="28" Name="Label1">List of Customers (Name AND Title :-) )</Label>
<ComboBox Height="23" Name="ComboBox1" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}" IsTextSearchEnabled="True" SelectionChanged="ComboBox1_SelectionChanged" SelectedValue="{Binding Path=CustomerID}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} - {1}">
<Binding Path="ContactName" />
<Binding Path="ContactTitle" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBox Height="23" Name="textBox1" Width="120"/>
</StackPanel>
</Grid>
</Window>
My ultimate goal would be to populate the TextBox dynamically by getting the selected value, and getting the info in the dataset associated with that CustomerID, but just getting the SelectedItem's text to populate in the TextBox would be a huge step.
Any help is GREATLY appreciated. Thanks all.
Give this a try; it removes the changed event handler and leverages binding.
<Grid Loaded="Grid_Loaded">
<StackPanel Name="StackPanel1" Margin="12">
<Label Height="28" Name="Label1">List of Customers (Name AND Title :-) )</Label>
<ComboBox Height="23" Name="ComboBox1" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}" IsTextSearchEnabled="True" SelectedValue="{Binding Path=CustomerID}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} - {1}">
<Binding Path="ContactName" />
<Binding Path="ContactTitle" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBox Height="23" Name="textBox1" Text="{Binding ElementName=ComboBox1, Path=SelectedItem.ContactName}" Width="120"/>
</StackPanel>
</Grid>
Check out this SO answer as well, which details the differences between SelectedItem, SelectedValue, and SelectedValuePath and is ultimately the issue most people run into.

Can I do Text search with multibinding

I have below combo box in mvvm-wpf application. I need to implement "Text search" in this..(along with multibinding). Can anybody help me please.
<StackPanel Orientation="Horizontal">
<TextBlock Text="Bid Service Cat ID"
Margin="2"></TextBlock>
<ComboBox Width="200"
Height="20"
SelectedValuePath="BidServiceCategoryId"
SelectedValue="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Path=DataContext.SelectedBidServiceCategoryId.Value}"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Path=DataContext.BenefitCategoryList}"
Margin="12,0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}: {1}">
<Binding Path="BidServiceCategoryId" />
<Binding Path="BidServiceCategoryName" />
</MultiBinding>
</TextBlock.Text></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
Unfortunately, TextSearch.Text doesn't work in a DataTemplate. Otherwise you could have done something like this
<ComboBox ...>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="TextSearch.Text">
<Setter.Value>
<MultiBinding StringFormat="{}{0}: {1}">
<Binding Path="BidServiceCategoryId"/>
<Binding Path="BidServiceCategoryName"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
However this won't work, so I see two solutions to your problem.
First way
You set IsTextSearchEnabled to True for the ComboBox, override ToString in your source class and change the MultiBinding in the TextBlock to a Binding
Xaml
<ComboBox ...
IsTextSearchEnabled="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
Source class
public class TheNameOfYourSourceClass
{
public override string ToString()
{
return String.Format("{0}: {1}", BidServiceCategoryId, BidServiceCategoryName);
}
//...
}
Second Way
If you don't want to override ToString I think you'll have to introduce a new Property in your source class where you combine BidServiceCategoryId and BidServiceCategoryName for the TextSearch.TextPath. In this example I call it BidServiceCategory. For this to work, you'll have to call OnPropertyChanged("BidServiceCategory"); when BidServiceCategoryId or BidServiceCategoryName changes as well. If they are normal CLR properties, you can do this in set, and if they are dependency properties you'll have to use the property changed callback
Xaml
<ComboBox ...
TextSearch.TextPath="BidServiceCategory"
IsTextSearchEnabled="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}: {1}">
<Binding Path="BidServiceCategoryId" />
<Binding Path="BidServiceCategoryName" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
Source class
public class TheNameOfYourSourceClass
{
public string BidServiceCategory
{
get
{
return String.Format("{0}: {1}", BidServiceCategoryId, BidServiceCategoryName);
}
}
private string m_bidServiceCategoryId;
public string BidServiceCategoryId
{
get
{
return m_bidServiceCategoryId;
}
set
{
m_bidServiceCategoryId = value;
OnPropertyChanged("BidServiceCategoryId");
OnPropertyChanged("BidServiceCategory");
}
}
private string m_bidServiceCategoryName;
public string BidServiceCategoryName
{
get
{
return m_bidServiceCategoryName;
}
set
{
m_bidServiceCategoryName = value;
OnPropertyChanged("BidServiceCategoryName");
OnPropertyChanged("BidServiceCategory");
}
}
}
I don't know if your text search has to search ALL the text, but if you want to search from the category ID, you can just set the TextSearch.TextPath property to BidServiceCategoryId. That should also be helpful for anyone who wants to use multibinding and finds that the text search no longer works... It does work if you explicitly set the TextPath property.

Resources