how to access a textbox in a grid in a stackpanel - c#

I have the following code:
<StackPanel x:Name="ContentStackPanel">
<Grid>
<TextBlock Text="Min Value" />
<TextBox Text="{Binding MinValue}" />
</Grid>
<Grid>
<TextBlock Text="Max Value" />
<TextBox Text="{Binding MinValue}" />
</Grid>
</StackPanel>
I want to add a button so that I can clear the text in both TextBoxes. This code doesn't work
private void ClearAllClick(object sender, RoutedEventArgs e)
{
foreach (TextBox tb in ContentStack.Children)
{
tb.Text = String.Empty;
}
}
how do I access the textbox inside the grid of ContentStackPanel?

The Children property only gives you immediate children, not all descendants. You could write a helper method to traverse the tree:
private void ClearAllClick(object sender, RoutedEventArgs e)
{
ClearTextChildren(ContentStackPanel);
}
private void ClearTextChildren((Panel container)
{
foreach (var element in container.Children)
{
if (element is TextBox)
((TextBox)element).Text = String.Empty;
else if (element is Panel)
ClearChildren((Panel)element);
}
}
An alternative approach (probably better, since it's fragile to traverse UI trees in code) would be to use a Command implementation on the button, instead of a click handler. This will allow you to clear the view-model properties instead of the text boxes themselves.
<Button x:Name="ClearAll" Command="{Binding ClearAllCommand}" />
"ClearAllCommand" should be in the same place as "MinValue" and "MaxValue":
public ICommand ClearAllCommand { get; private set; }
Using a standard DelegateCommand implementation:
ClearAllCommand = new DelegateCommand(arg => {
MinValue = null;
MaxValue = null;
});

Related

How to pass arguments between pages in XAML without causing loading issues

Apologies in advance I have less than a month of XAML and WPF experience and have been googling as much as possible but here we go.
I have a window that has a single frame inside of it, I have been using pages that swap out what is inside the frame. I have been doing this like this for example:
private void NextPageButtonPressed(object sender, RoutedEventArgs e)
{
this.NavigationService.Navigate(new Page2())
}
This works just fine for navigation when data is not being passed but when I try to go from page2 to page3 and try to pass data from various checkboxes and combo boxes as so:
private void GoToNextPage(object sender, RoutedEventArgs e)
{
object[] isKaliAndDistroSelected = new object[] {GetIsKali(), GetSelectedDistro()};
Page3 pg3 = new Page3();
this.NavigationService.LoadCompleted += pg3.NavigationService_LoadCompleted;
this.NavigationService.Navigate(pg3, isKaliAndDistroSelected);
}
with the following code on Page3:
private string distro="";
private bool isKali = false;
public Page3()
{
InitializeComponet();
RunOnDoneLoadingPage(distro,isKali);
}
public void NavigationService_LoadCompleted(object sender, NavigationEventArgs e)
{
var test = (object[]) e.ExtraData;
isKali = (bool) test[0];
distro = (string) test[1];
NavigationService.LoadCompleted -= NavigationService_LoadCompleted;
}
it ends up not updating the values isKali or distro unless I place the RunOnDoneLoadingPage(distro,isKali); at the end of the NavigationService_LoadCompletedfunction on Page3. The reason I do not want this is because I want the page to load and THEN run the RunOnDoneLoadingPage() because the function manipulates a loading/progress bar. When I place it in the NavigationService_LoadCompleted page2 always freezes for a few before displaying Page3 with a loading/progress at 100% making the loading page essentially useless.
So my question is how can I pass data between 2 pages and have the next page display itself but not start a method until the data from the previous page has been passed
Thank you in advance, like I said I'm fairly new to working with XAML so if I can provide more please let me know
MVVM can help you.
this is a simple view model:
public class MyVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string propAOfPage1;
public string PropAOfPage1
{
get => propAOfPage1;
set
{
if (value != this.propAOfPage1)
{
propAOfPage1= value;
NotifyPropertyChanged();
}
}
}
private string propBOfPage1;
public string PropBOfPage1
{
get => propBOfPage1;
set
{
if (value != this.propBOfPage1)
{
propBOfPage1= value;
NotifyPropertyChanged();
}
}
}
private string propCOfPage2;
public string PropCOfPage2
{
get => propCOfPage2;
set
{
if (value != this.propCOfPage2)
{
propCOfPage2= value;
NotifyPropertyChanged();
}
}
}
private string propDOfPage2;
public string PropDOfPage2
{
get => propDOfPage2;
set
{
if (value != this.propDOfPage2)
{
propDOfPage2= value;
NotifyPropertyChanged();
}
}
}
}
and here is a simple view:
<!-- Page1 -->
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Prop A:" />
<TextBox Text="{Binding PropAOfPage1}" Width="100" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Prop B:" />
<TextBox Text="{Binding PropBOfPage1}" Width="100" />
</StackPanel>
</StackPanel>
<!-- Page2 -->
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Prop C:" />
<TextBox Text="{Binding PropCOfPage2}" Width="100" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Prop D:" />
<TextBox Text="{Binding PropDOfPage2}" Width="100" />
</StackPanel>
</StackPanel>
just set Frame.DataContext as a MyVM object, them this MyVM object will share between your pages. you can navigate freely and need not consider to get or set the control property in each page.
Extended reading: https://www.google.com/search?q=wpf+frame+page+mvvm

Is it possible to write some logic for a viewcell and get a value from this this viewcell field?

Is it possible to create a ListView with ViewCells that will contain two Buttons and Label, first button will be "+", second "-" and a label will be a counter that will show how much "+" button has been tapped.
Then I want to be able to get from my listview an item that is binded to this viewcell and information about how much this item has been selected.
For now I created a StackLayout filled with Views thats "mocks" a Viewcells. This solution is so bad for many items because I have to create lots of Views (it takes few seconds).
So I would like to solve the problem using a ListView but I have no idea how to achive this. Or maybe you have a better solution than using a listview?
this should be trivial. First, create a data structure to hold your data
public class MyData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private double _count;
public double Count
{
get
{ return _count; }
set
{
_count = value;
NotifyPropertyChanged();
}
}
List<MyData> data { get; set; }
you will need to initialize it with as many rows as your want to display in your list. The create a template with a Label and Buttons that are bound to your Count property
<ListView x:Name="listView" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Count}" />
<Button Clicked="Increment" CommandParameter="{Binding .}" Text="+" />
<Button Clicked="Decrement" CommandParameter="{Binding .}" Text="-" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
in your code-behind
protected void Decrement(object sender, EventArgs args) {
var b = (Button)sender;
var data = (MyData)b.CommandParameter;
data.Count--;
}
protected void Increment(object sender, EventArgs args) {
var b = (Button)sender;
var data = (MyData)b.CommandParameter;
data.Count++;
}
finally, use binding or direct assignment to set the List's ItemsSourcee
listView.ItemsSource = data;

Access ListBoxItem children

I have a ListBox which gets populated dynamically by my own class. This is an example of my listbox:
<ListBox x:Name="mylistbox" SelectionChanged="timelinelistbox_SelectionChanged_1">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding userid}" Visibility="Collapsed" />
<TextBlock Text="{Binding postid}" Visibility="Collapsed" />
<Image Source="{Binding thumbnailurl}" />
<TextBlock Text="{Binding username}" />
<TextBlock Text="{Binding description}" />
<Image Source="{Binding avatar}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When the SelectedItemChanged event of the ListBox gets triggered I get my ListBoxItem.
But now I want to alter the children in that ListBoxItem... But I can't seem to access the children of the ListBoxItem?
I tried:
private void timelinelistbox_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
{
//Get the data object that represents the current selected item
MyOwnClass data = (sender as ListBox).SelectedItem as MyOwnClass;
//Get the selected ListBoxItem container instance
ListBoxItem selectedItem = this.timelinelistbox.ItemContainerGenerator.ContainerFromItem(data) as ListBoxItem;
// change username and display
data.username = "ChangedUsername";
selectedItem.Content = data;
}
But the username doesn't change...
You don't have to change back Content of selected ListBoxItem. MyOwnClass is a class, I assume, and therefore reference type so changing username in one instance will have effect in all references to the same object. Your MyOwnClass should implement INotifyPropertyChanged interface (MSDN) and raise PropertyChanged event each time property changes. Like that you notify all bound controls that the property has changed and need refreshing:
public class MyOwnClass : INotifyPropertyChanged
{
private string _username;
public string username
{
get { return _username ; }
set
{
if (_userName == value) return;
_userName = value;
NotifyPropertyChanged("username");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
and then it will be enough if you do:
private void timelinelistbox_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
{
((sender as ListBox).SelectedItem as MyOwnClass).username = "ChangedUsername";
}

How to get lable name in dynamically?

<Grid x:Name="LayoutRoot">
<TextBox x:Name="txt_diplay_1" HorizontalAlignment="Left" Height="42" Margin="155,78,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="103.5" GotFocus="txt_diplay_1_GotFocus" />
<TextBox x:Name="txt_diplay_2" Height="42" Margin="297,78,239.5,0" TextWrapping="Wrap" VerticalAlignment="Top" GotFocus="txt_diplay_2_GotFocus" />
<Button x:Name="btn_a" Content="A" HorizontalAlignment="Left" Height="40" Margin="155,147,0,0" VerticalAlignment="Top" Width="73" Click="btn_a_Click" />
<Button x:Name="btn_b" Content="B" Height="40" Margin="237,147,0,0" VerticalAlignment="Top" Click="btn_b_Click" HorizontalAlignment="Left" Width="73" />
<Button x:Name="btn_c" Height="40" Margin="0,147,239.5,0" VerticalAlignment="Top" HorizontalAlignment="Right" Width="73" Click="btn_c_Click" >
<Grid Height="30.833" Width="61.5">
<Label x:Name="lbl_1" Content="1" Margin="22.498,6.5,19.501,2.166"/>
<Label x:Name="lbl_2" Content="!" HorizontalAlignment="Right" Margin="0,-4.422,0,13.088" Width="19.501"/>
</Grid>
</Button>
</Grid>
The design will be like this
public partial class MainWindow : Window
{
Control TexboxDetails = null;
Control ButtonDetails;
Button BehaveButton;
public MainWindow()
{
this.InitializeComponent();
}
private void btn_a_Click(object sender, RoutedEventArgs e)
{
ButtonDetails = (Control)sender;
all_in_one();
}
private void btn_b_Click(object sender, RoutedEventArgs e)
{
ButtonDetails = (Control)sender;
all_in_one();
}
private void btn_c_Click(object sender, RoutedEventArgs e)
{
}
private void txt_diplay_1_GotFocus(object sender, RoutedEventArgs e)
{
TexboxDetails = (Control)sender;
}
private void txt_diplay_2_GotFocus(object sender, RoutedEventArgs e)
{
TexboxDetails = (Control)sender;
}
public void all_in_one()
{
BehaveButton = ButtonDetails as Button;
if (TexboxDetails != null)
{
TextBox BehaveTextbox = TexboxDetails as TextBox;
var caret_index = BehaveTextbox.CaretIndex;
BehaveTextbox.Text = BehaveTextbox.Text.Insert(caret_index, BehaveButton.Content.ToString());
BehaveTextbox.Focus();
BehaveTextbox.CaretIndex = caret_index + 1;
}
}
}
With above code i can get Button name dynamically when i click that button.
In above figure one button(btn_c) has two labels. now i want get that separate labels name dynamcially when i click button(btn_c).
You can get them like this (inside the btn_c click handler):
var btn_c = (Button)sender;
Grid grid = (Grid)btn_c.Content;
Label label1 = (Label)grid.Children[0];
string name1 = label1.Name;
Your whole design could really use some rework. Take a look at this code: (notice the reduced number of event handlers; you'll need to modify the XAML to use these)
public partial class MainWindow : Window
{
TextBox LastFocusedTextBox;
public MainWindow()
{
this.InitializeComponent();
}
private void btn_Click(object sender, RoutedEventArgs e)
{
InsertButtonContent((Button)sender);
}
private void txt_diplay_GotFocus(object sender, RoutedEventArgs e)
{
LastFocusedTextBox = (TextBox)sender;
}
public void InsertButtonContent(Button button)
{
if (LastFocusedTextBox != null)
{
string buttonContentString = button.Content as string;
if (string.IsNullOrEmpty(buttonContentString))
{
var grid = button.Content as Grid;
if (grid != null)
buttonContentString = string.Join("", grid.Children.OfType<ContentControl>().Select(x => x.Content));
}
var caret_index = LastFocusedTextBox.CaretIndex;
LastFocusedTextBox.Text = LastFocusedTextBox.Text.Insert(caret_index, buttonContentString);
LastFocusedTextBox.Focus();
LastFocusedTextBox.CaretIndex = caret_index + buttonContentString.Length;
}
}
}
Notice how the Button are passed to the method instead of being stored in a field. Also, unnecessary fields, both in the class and local to the all_in_one() method were removed. To get the contents of the labels in the Grid (e.g. "1!" - I assume this is what you were after, since nothing else could go into a simple string field, and also match the general pattern of your first two buttons), we simply select their contents and join them into a single string, after checking if the content was a string or a Grid.

Attached property attached only on the first user control instance

Following Josh Smith example on mvvm workspaces (customers view), I have a mainwindow and a mainwindowviewmodel which contains an ObservableCollection of "ChatTabViewModel":
internal class FriendsListViewModel : ObservableObject
{
#region bound properties
private ICollectionView viewfriends;
private ObservableCollection<ChatTabViewModel> _chatTab;
...
#endregion
}
I have an area dedicated to this collection in the xaml like that :
<ContentControl Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" Content="{Binding Path=ChatTabs}" ContentTemplate="{StaticResource ChatTabsTemplate}" />
And in my resources dictionary:
<DataTemplate DataType="{x:Type vm:ChatTabViewModel}">
<View:ChatTabView />
</DataTemplate>
<DataTemplate x:Key="ClosableTabItemTemplate">
<DockPanel>
<Button
Command="{Binding Path=CloseCommand}"
Content="X"
Cursor="Hand"
DockPanel.Dock="Right"
Focusable="False"
FontFamily="Courier"
FontSize="9"
FontWeight="Bold"
Margin="0,1,0,0"
Padding="0"
VerticalContentAlignment="Bottom"
Width="16" Height="16"
/>
<ContentPresenter
Content="{Binding Path=Caption, Mode=OneWay}"
VerticalAlignment="Center">
</ContentPresenter>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="ChatTabsTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"/>
</DataTemplate>
On user event I add a new ChattabViewModel in my collection and the view related to it appears in the main window.
But when I tried to add an attached property on a scrollbar in the ChattabView, this property will attach only on the first ChattabViewModel instance, the other tabs won't be bound to the attached property. Here's the ChattabView XAML:
<ScrollViewer VerticalScrollBarVisibility="Auto" Grid.Row="0">
<ItemsControl ItemsSource="{Binding Messages}" View:ItemsControlBehavior.ScrollOnNewItem="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox IsReadOnly="True" TextWrapping="Wrap" Text="{Binding Path=DataContext, RelativeSource={RelativeSource Self}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
and the code of the attached property:
namespace GtalkOntre.View
{
/// <summary>
/// Util class to scroll down when a new message is added.
/// </summary>
/// <remarks>attached property called ScrollOnNewItem that when set to true hooks into the INotifyCollectionChanged events of the itemscontrol items source and upon detecting a new item, scrolls the scrollbar to it.</remarks>
public class ItemsControlBehavior
{
static Dictionary<ItemsControl, Capture> Associations = new Dictionary<ItemsControl, Capture>();
public static bool GetScrollOnNewItem(DependencyObject obj)
{
return (bool)obj.GetValue(ScrollOnNewItemProperty);
}
public static void SetScrollOnNewItem(DependencyObject obj, bool value)
{
obj.SetValue(ScrollOnNewItemProperty, value);
}
public static DependencyProperty ScrollOnNewItemProperty =
DependencyProperty .RegisterAttached(
"ScrollOnNewItem",
typeof(bool),
typeof(ItemsControlBehavior),
new UIPropertyMetadata(false, OnScrollOnNewItemChanged));
public static void OnScrollOnNewItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var mycontrol = d as ItemsControl;
if (mycontrol == null) return;
bool newValue = (bool)e.NewValue;
if (newValue)
{
mycontrol.Loaded += MyControl_Loaded;
mycontrol.Unloaded += MyControl_Unloaded;
}
else
{
mycontrol.Loaded -= MyControl_Loaded;
mycontrol.Unloaded -= MyControl_Unloaded;
if (Associations.ContainsKey(mycontrol))
Associations[mycontrol].Dispose();
}
}
static void MyControl_Unloaded(object sender, RoutedEventArgs e)
{
var mycontrol = (ItemsControl)sender;
Associations[mycontrol].Dispose();
mycontrol.Unloaded -= MyControl_Unloaded;
}
static void MyControl_Loaded(object sender, RoutedEventArgs e)
{
var mycontrol = (ItemsControl)sender;
var incc = mycontrol.Items as INotifyCollectionChanged;
if (incc == null) return;
mycontrol.Loaded -= MyControl_Loaded;
Associations[mycontrol] = new Capture(mycontrol);
}
class Capture : IDisposable
{
public ItemsControl mycontrol { get; set; }
public INotifyCollectionChanged incc { get; set; }
public Capture(ItemsControl mycontrol)
{
this.mycontrol = mycontrol;
incc = mycontrol.ItemsSource as INotifyCollectionChanged;
incc.CollectionChanged +=incc_CollectionChanged;
}
void incc_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
ScrollViewer sv = mycontrol.Parent as ScrollViewer;
sv.ScrollToBottom();
}
}
public void Dispose()
{
incc.CollectionChanged -= incc_CollectionChanged;
}
}
}
}
So why is the attached property only bound once, on the first "chattabview" occurence of the chattabviewmodel collection? and therefore, working only on the first chattabviewmodel.
When I close them all, the attached property will unbind itself on the last instance of chattabviewmodel, and when I add a new first chattabviewmodel, the property will bind correctly. So it triggers only on the first instance and last instance of the "chattabviewmodel" collection of mainwindowviewmodel.
After a week of searching, I'm a little desperate now...
So far my hypothesis is : the problem might be related to the way I set the view to my viewmodel in dictionary resources. The view might be shared and the first scrollbar only might react. I tried to add an x:Shared = false attribute on the DataTemplate tag but it didn't change anything.
Are you sure there are different instances of your ChatTabView being created?
I believe WPF's TabControl re-uses the existing template if it's the same instead of creating a new one, and simply replaces the DataContext behind it.
So it would only create one copy of your ChatTabView and switching tabs is replacing the DataContext behind the ChatTabView to a different item in the collection.
You haven't shown us ChatTabsTemplate, so I can only assume it contains a TabControl. If so, that explains the behavior you're seeing. The TabControl lazily loads its child tab items, so only the current view will be initialized, and hence have the attached property applied to it. When you switch tabs, however, you should see the same attached property firing. Is that not the case?
As for your hunch, it's not quite right. The DataTemplate is being shared, but the DataTemplate is used to create distinct instances of its contents, which are not being shared.

Categories

Resources