Binding string to a textbox based on which button is clicked - c#

I have an editor (textbox) and I'd like to bind different texts to it based on which button is clicked.
I could use a command on the button and pass the string which I'd like to edit through commandparameter and update the string which is bound to the textbox. This will work but it's not going to save the modifications because there is no binding between the text (which was passed through the commandparameter) and the textbox's text.
My question is, how should I implement this bind neatly without accessing the textbox directly from the View Model?
edit: it's probably vague what I'm trying to achieve. I try to clarify it a bit:
So let's say I have several different buttons, if I click one of those, it should bind some string to the editor's textbox, where I can modify it and save it later.
<Button Content="Edit query" Command="{Binding ShowQueryInEditorCommand}" CommandParameter="{Binding SomeSqlStringToBeEdited}"/>
<Button Content="Edit query" Command="{Binding ShowQueryInEditorCommand}" CommandParameter="{Binding SomeOtherSqlStringToBeEdited}"/>
This is what the command will execute:
public void ShowQueryInEditor(object o)
{
string SqlStatementParam = o as string;
if (SqlStatementParam != null)
SQLStatement = SqlStatementParam;
}
And the editor TextBox itself:
<TextBox Text="{Binding SQLStatement}">
As you can see, this is very rudimentary as it just sets the SQLStatement string, but there is no bind between them so it cannot reflect the modifications back to SomeSqlStringToBeEdited/SomeOtherSqlStringToBeEdited. This is what I would like to achieve, to bind that string somehow to the textbox when the button is clicked.

There are two basic ways I can think of: through code, or through Xaml.
In code, instead of accessing the textbox from the ViewModel, add a new property to the ViewModel for the "DisplayText" or "SelectedText", or whatever makes sense in your scenario. Bind your textbox to that property instead, and then put the rest of the logic you need inside the setter (or, if it's a DependencyProperty, the OnPropertyChanged callback). That keeps all the logic in your ViewModel and means the Xaml doesn't have to care.
Or in Xaml, you could use triggers and templates to change the textbox depending on the selected button. Most likely form your description, I would suggest having multiple textboxes, one bound to each string, and switch the visible textbox based on the button that's clicked. This keeps your ViewModel ignorant of this display-specific logic, and allows you to change it more easily later on.
Personally, I would likely suggest the Xaml approach, but it will depend on your specific situation.

According to
but the problem is that the buttons are created dynamically
1) Wrap you query text and button into view model like this:
public class ViewModel : ViewModelBase
{
public ViewModel()
{
this.turnIsSelectedOnCommand = new RelayCommand(() => IsSelected = true);
}
public String Text
{
get { return text; }
set
{
if (text != value)
{
text = value;
OnPropertyChanged("Text");
}
}
}
private String text;
public Boolean IsSelected
{
get { return isSelected; }
set
{
if (isSelected != value)
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
private Boolean isSelected;
public RelayCommand TurnIsSelectedOnCommand
{
get { return turnIsSelectedOnCommand; }
}
private readonly RelayCommand turnIsSelectedOnCommand;
}
2) Put your dynamically created text/buttons into collection. For simplicity, I've added them to array:
public MainWindow()
{
InitializeComponent();
DataContext = new[]
{
new ViewModel { Text = "SELECT * FROM Foo", IsSelected = true },
new ViewModel { Text = "SELECT * FROM Bar" },
new ViewModel { Text = "DROP TABLE Foo" },
new ViewModel { Text = "DROP TABLE Bar" },
};
}
3) Bind the collection with ListBox, and the editor - with the Text of the selected item:
<ListBox Grid.Row="0" Margin="5" ItemsSource="{Binding}" x:Name="lbItems">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center"
HorizontalAlignment="Right"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Transparent"/>
</Style.Resources>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="Edit query" Command="{Binding TurnIsSelectedOnCommand}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Grid.Row="1" Margin="5" Text="{Binding Path=SelectedItem.Text, ElementName=lbItems}" />
I've added some style modifications.
First modifies buttons layout.
Second means, that when you will push the button and ViewModel will become selected, list box item will be selected too.
Third hides selection from the background of the selected item.
Hope, this helps.

Related

Label Visibility.Collapsed on empty content WPF MVVM

I'm trying to figure out how I can make hide a label when it's hidden & visible again when text is set using XAML. I can easily do it with a TextChanged event but there must be a correct way to do it using xaml, right?
I've done my research but I can't find anything that works.
Window1
<TextBox Name="nameTxt" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
Window2
<Label Name="nameLbl" Content="{Binding Name}" />
Basically it implements by built-in class in .Net BooleanToVisibilityConverter:
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="booleanVisibilityConverter"/>
</UserControl.Resources>
Let me show work example:
XAML:
<Button Content="Hello, I am the button" Visibility="{Binding ShowButton,
Converter={StaticResource booleanVisibilityConverter}}"/>
ViewModel:
private bool _showButton = false;
public bool ShowButton
{
get { return _showButton; }
set
{
if (value != _showButton)
{
_showButton = value;
OnPropertyChanged("ShowButton");
}
}
}
If you want to change the Visibility of your Button in code, you can make it by this code in ViewModel:
IsButtonVisible = false;
Add a custom converter.
wpf tutorial value converters
You can write the logic to test for string.IsNullOrEmpty and return hidden, otherwise return visible.
This approach is nice as it keeps visibility out of your view model.

WPF - switching between two way and one way bindings?

In my 'View' I have a TextBox bound to a ViewModel's string property.
I want to add a submit button to the View, so the underlying ViewModels string property is only updated when this is pressed.
To further complicate things, this TextBox is inside a DataGrid. I think setting the bindings UpdateSourceTrigger to Explicit may be the answer but I can't see how this would work.
Any alternative solution would be to switch the ViewModels String with a TextBox - meaning I would manually populate data.
You can bind button to command and pass text of textbox as parameter.
<TextBox x:Name="textBox"></TextBox>
<Button Content="Button" Command="{Binding MyCommand}" CommandParameter="{Binding ElementName=textBox, Path=Text}"/>
In your ViewModel:
public ICommand MyCommand
{
get
{
return new RelayCommand((textBoxText) =>
{
if (...)
{
//somelogic;
}
});
}
}
In the Button you access the row via the DataContext
private void ButtonRevise_Click(object sender, RoutedEventArgs e)
{
Button btn = (Button)sender;
GabeLib.SearchItem srchItem = (GabeLib.SearchItem)btn.DataContext;

change textblock text that is inside Listbox in windowsphone 8

i want to change textblock text in page initialize event
here is my xaml
<ListBox Margin="3,60,1,10" BorderThickness="2" Grid.Row="1" Name="lstAnnouncement" Tap="lstAnnouncement_Tap" Width="476" d:LayoutOverrides="VerticalMargin">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="thispanel" Grid.Row="1" Orientation="Horizontal" Height="120" Width="478" >
<StackPanel.Background>
<ImageBrush ImageSource="Images/Text-ALU.png" Stretch="Fill" />
</StackPanel.Background>
<Grid HorizontalAlignment="Left" Width="30" Margin="0,0,0,2" Background="#FF0195D5" Height="118">
<TextBlock x:Name="txtDate" TextWrapping="Wrap">
</TextBlock>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
i want to change txtDate.Text using c# in code-behind but txtdate is not accessible in code behind so how to achieve it ?
The reason you're not able to access the txtDate object is because it's contained within the DataTemplate you're using for the ListBox. This isn't an error - the DataTemplate is being applied to every single item added to your ListBox.
Given that the ListBox creates, among other controls, a Grid containing a TextBlock with the name "txtDate", for every single item added to it, what would it mean to access the txtDate object? How would your program decide which of a (functionally) infinite number of txtDates associated with an identical number of ListBoxItems you meant when you referenced txtDate?
If you wanted to be able to easily change the content of txtDate, you'd want to bind the ItemsSource of your ListBox to a property in a ViewModel. The easiest way to do this would be to have that property be an IEnumerable containing a custom model type. This way, you could update the text property of that model and call NotifyPropertyChanged on the that property, and the UI would update to reflect the new data.
Here's an example:
public class YourViewModel
{
public List<YourModel> Models { get; set; }
}
public class YourModel : INotifyPropertyChanged
{
private string yourText;
public string YourText
{
get { return yourText; }
set
{
yourText = value;
NotifyPropertyChanged("YourText");
}
}
// add INotifyPropertyChanged implementation here
}
And then you'd want to bind the ItemsSource of the ListBox to YourViewModel's Models property, and the text of your TextBox to the YourModel's YourText property. Any time you change the YourModel.YourText property, it'll automatically update on the UI. I think it's probably subject to debate whether having your model implement INotifyPropertyChanged is proper MVVM, but I find it a lot easier in these cases than forcing the ViewModel to update every single model each time a change is made on one of them.
If you're not familiar with the MVVM pattern used with WPF, this might be a good start: MVVM example.
this function will help you... This will help u find the control inside of a listbox runtime..
public FrameworkElement SearchVisualTree(DependencyObject targetElement, string elementName)
{
FrameworkElement res = null;
var count = VisualTreeHelper.GetChildrenCount(targetElement);
if (count == 0)
return res;
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(targetElement, i);
if ((child as FrameworkElement).Name == elementName)
{
res = child as FrameworkElement;
return res;
}
else
{
res = SearchVisualTree(child, elementName);
if (res != null)
return res;
}
}
return res;
}
Here first parameter is parent and the second parameter is the name of the element which in your case is "txtDate".. hope it works!!

Change GridView Item DataTemplate based on seperate ViewModel Property in WinRT

What I am attempting to do is have a collection of items shown in a GridView control and have the size of these items change based on a command executed by a separate button.
For example, having a row of buttons across the top reading “Small”, “Medium” and “Large” and having the items in the GridView respond to the relevant command by displaying its items in the relevant state.
I have the gridview declared like so
<GridView ItemsSource="{Binding Squares}"
With Squares being an observable collection of Square objects that have a Title and a Fill property.
At first I went down the DataTemplateSelector route by declaring the following data templates in the Resources section of the page.
<DataTemplate x:Key="SquareSmallTemplate">
<Grid Height="100" Width="100">
<Rectangle Fill="{Binding Fill}"/>
<TextBlock Text="{Binding Title}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="SquareMediumTemplate">
<Grid Height="150" Width="150">
<Rectangle Fill="{Binding Fill}"/>
<TextBlock Text="{Binding Title}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="SquareLargeTemplate">
<Grid Height="200" Width="200">
<Rectangle Fill="{Binding Fill}"/>
<TextBlock Text="{Binding Title}"/>
</Grid>
</DataTemplate>
The idea being that the grid’s height and width properties are different for the relevant template. I declared the following data templates in the selector
public DataTemplate SmallTemplate { get; set; }
public DataTemplate MediumTemplate { get; set; }
public DataTemplate LargeTemplate { get; set; }
And in the SelecteTemplateCore method I just returned the relevant template
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
string value = item as string;
if (value != null)
{
if (value == "Small")
return SmallTemplate;
else if (value == "Medium")
return MediumTemplate;
else if (value == "Large")
return LargeTemplate;
return base.SelectTemplate(item, container);
}
else
{
return base.SelectTemplateCore(item, container);
}
}
However, with this method (and, by design of the DataTemplateSelector) the object being passed in is the item in the collection (the Square).
This is fine if I wanted each item to have a different appearance or something, but what I need is the template to change based on another property on the view model.
For this, I have the following
public string State {get; set;}
and this is set to “Small”, “Medium, or “Large based on a separate row of three buttons that execute a command that sets this property to the relevant value.
How do I relate the State property to changing to the relevant DataTemplate?
Another route I tried was to have a single Data template that used the VSM to animate the Height/Width properties in the relevant states. However I could not get the relevant animation to execute when the State changed.
Any help would be great, thanks
There are a few ways to do this, I'm not sure which would be best. In any case, you'll need 1) a trigger, and 2) the action to update the template. I am leaning towards using PropertyChangedTrigger along with an InvokeCommandAction.
<GridView x:Name="grid">
<i:Interaction.Triggers>
<ei:PropertyChangedTrigger Binding="{Binding State}">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=UpdateTemplateCommand}" CommandParameter="{Binding State}" />
</ei:PropertyChangedTrigger>
</i:Interaction.Triggers>
<GridView>
(Here the "AncestorType" would just be the root of the view, so please change "UserControl" as needed.)
Then in the view, you would have an ICommand that updates the template:
UpdateTemplateCommand = new DelegateCommand(state => {
switch ((string)state)
{
default:
case "Small" : grid.ItemTemplate = "SquareSmallTemplate"; break;
case "Medium" : grid.ItemTemplate = "SquareMediumTemplate"; break;
case "Large" : grid.ItemTemplate = "SquareLargeTemplate"; break;
}
});
IDK ... after writing this out it seems a bit convoluted. Maybe you'd find it preferable to add a CurrentDataTemplate property to the view-model, and assign it by creating DataTemplates from strings using XamlReader.

Select item programmatically in WPF ListView

I'm unable to figure out how to select an item programmatically in a ListView.
I'm attempting to use the listview's ItemContainerGenerator, but it just doesn't seem to work. For example, obj is null after the following operation:
//VariableList is derived from BindingList
m_VariableList = getVariableList();
lstVariable_Selected.ItemsSource = m_VariableList;
var obj =
lstVariable_Selected.ItemContainerGenerator.ContainerFromItem(m_VariableList[0]);
I've tried (based on suggestions seen here and other places) to use the ItemContainerGenerator's StatusChanged event, but to no avail. The event never fires. For example:
m_VariableList = getVariableList();
lstVariable_Selected.ItemContainerGenerator.StatusChanged += new EventHandler(ItemContainerGenerator_StatusChanged);
lstVariable_Selected.ItemsSource = m_VariableList;
...
void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
//This code never gets called
var obj = lstVariable_Selected.ItemContainerGenerator.ContainerFromItem(m_VariableList[0]);
}
The crux of this whole thing is that I simply want to pre-select a few of the items in my ListView.
In the interest of not leaving anything out, the ListView uses some templating and Drag/Drop functionality, so I'm including the XAML here. Essentially, this template makes each item a textbox with some text - and when any item is selected, the checkbox is checked. And each item also gets a little glyph underneath it to insert new items (and this all works fine):
<DataTemplate x:Key="ItemDataTemplate_Variable">
<StackPanel>
<CheckBox x:Name="checkbox"
Content="{Binding Path=ListBoxDisplayName}"
IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}, Path=IsSelected}" />
<Image ToolTip="Insert Custom Variable" Source="..\..\Resources\Arrow_Right.gif"
HorizontalAlignment="Left"
MouseLeftButtonDown="OnInsertCustomVariable"
Cursor="Hand" Margin="1, 0, 0, 2" Uid="{Binding Path=CmiOrder}" />
</StackPanel>
</DataTemplate>
...
<ListView Name="lstVariable_All" MinWidth="300" Margin="5"
SelectionMode="Multiple"
ItemTemplate="{StaticResource ItemDataTemplate_Variable}"
SelectionChanged="lstVariable_All_SelectionChanged"
wpfui:DragDropHelper.IsDropTarget="True"
wpfui:DragDropHelper.IsDragSource="True"
wpfui:DragDropHelper.DragDropTemplate="{StaticResource ItemDataTemplate_Variable}"
wpfui:DragDropHelper.ItemDropped="OnItemDropped"/>
So what am I missing? How do I programmatically select one or more of the items in the ListView?
Bind the IsSelected property of the ListViewItem to a property on your model. Then, you need only work with your model rather than worrying about the intricacies of the UI, which includes potential hazards around container virtualization.
For example:
<ListView>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsGroovy}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Now, just work with your model's IsGroovy property to select/deselect items in the ListView.
Where 'this' is the ListView instance. This will not only change the selection, but also set the focus on the newly selected item.
private void MoveSelection(int level)
{
var newIndex = this.SelectedIndex + level;
if (newIndex >= 0 && newIndex < this.Items.Count)
{
this.SelectedItem = this.Items[newIndex];
this.UpdateLayout();
((ListViewItem)this.ItemContainerGenerator.ContainerFromIndex(newIndex)).Focus();
}
}
Here would be my best guess, which would be a much simpler method for selection. Since I'm not sure what you're selecting on, here's a generic example:
var indices = new List<int>();
for(int i = 0; i < lstVariable_All.Items.Count; i++)
{
// If this item meets our selection criteria
if( lstVariable_All.Items[i].Text.Contains("foo") )
indices.Add(i);
}
// Reset the selection and add the new items.
lstVariable_All.SelectedIndices.Clear();
foreach(int index in indices)
{
lstVariable_All.SelectedIndices.Add(index);
}
What I'm used to seeing is a settable SelectedItem, but I see you can't set or add to this, but hopefully this method works as a replacement.
In case you are not working with Bindings, this could also be a solution, just find the items in the source and add them to the SelectedItems property of your listview:
lstRoomLights.ItemsSource = RoomLights;
var selectedItems = RoomLights.Where(rl => rl.Name.Contains("foo")).ToList();
selectedItems.ForEach(i => lstRoomLights.SelectedItems.Add(i));

Categories

Resources