User Control in Pivot, binding not work - c#

I'm developing windows phone 8 app. I have a customer UserControl called SelectableButton. The constructor of it is as below:
public SelectableButton()
{
InitializeComponent();
DataContext = this;
}
The xaml of it is like this:
<Grid>
<TextBlock x:Name="ButtonTextBlock"
Text="{Binding SelectableButtonText, Mode=TwoWay}"
SomeOtherCode
/>
...
</Grid>
The SelectableButtonText is a property of this UserControl:
public static readonly DependencyProperty SelectableButtonTextProperty =
DependencyProperty.Register(
"SelectableButtonText", typeof(string),
typeof(SelectableButton),
null
);
Now I use this SelectableButton in a Pivot. I want to bind the SelectableButtonText property to some data. This is the DataTemplate used in a Pivot called PivotTestContent:
<ShareControl:SelectableButton
SelectableButtonText="{Binding question}"
...
>
</ShareControl:SelectableButton>
The question is from the ItemsSource of this Pivot:
PivotTestContent.ItemsSource = quizs;
The quizs is a List<> of WCCQuizText
quizs = new List<WCCQuizText>();
And the question is a property member of WCCQuizText:
public String question
{
get;
set;
}
After all these work, I find that the Binding cant find the property question. It seems that because of this line in the constructor of SelectableButton:
DataContext = this;
The Binding will look for the property question in Class SelectableButton, not from the ItemsSouce. Because if I bind question directly to some TextBlock.Text, it will work. But when I bind it to my UserControl, it can't be found.
So anybody know how to deal with this?
If I do like this, I can show the binding text correctly, the TextBlock is in the Pivot, too.
<TextBlock
Name="TextBlockQuestion"
Text="{Binding question}"
....
>
</TextBlock>
And my Binding:
<ShareControl:SelectableButton
SelectableButtonText="{Binding Text, ElementName=TextBlockQuestion}"
....
>
</ShareControl:SelectableButton>

You are correct. It is caused by DataContext = this. Normally your UserControl would have context set to an instance of WCCQuizText but you are overwriting it with an instance of your UserControl. Try removing that line, give UserControl some name and and change your binding, within UserControl, to something like:
<UserControl x:Name="SomeName" ... >
....
<TextBlock ... Text="{Binding ElementName=SomeName, Path=SelectableButtonText}"
also TextBlock is display control and it will always be one way binding so you can skip Mode=TwoWay

Related

Setting ListView ItemsPanel Duplicates Items

I have a custom control in my application. One of the dependency properties is an ObservableCollection<ToggleButton>:
public ObservableCollection<ToggleButton> HeaderButtons
{
get { return (ObservableCollection<ToggleButton>)GetValue(HeaderButtonsProperty); }
set { SetValue(HeaderButtonsProperty, value); }
}
public static readonly DependencyProperty HeaderButtonsProperty = DependencyProperty.Register("HeaderButtons", typeof(ObservableCollection<ToggleButton>), typeof(Expandable), new PropertyMetadata(new ObservableCollection<ToggleButton>()));
I'm then putting them in a ListView in Generic.xaml:
<ListView ItemsSource="{TemplateBinding HeaderButtons}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
...and using it like this:
<controls:MyControl.HeaderButtons>
<ToggleButton x:Name="FilterButton">
<Image Source="/Assets/Icons/Empty Filter-512.png" Height="15" Width="15"/>
</ToggleButton>
</controls:MyControl.HeaderButtons>
However, I keep ending up with a duplicate item:
I can't figure out how that item is getting there. I can fix it by removing my custom ListView.ItemsPanel, but of course that makes my items flow vertically, defeating the entire purpose. Can anyone else see why this would be duplicating the item?
EDIT: For further interest, if I go into the Live Visual Tree I can see that both buttons have the name "FilterButton". Which should, of course, not be possible.
EDIT: Here's the ContentPresenter from the MainWindow:
<ContentPresenter Content="{Binding CurrentControl, Mode=OneWay}" Grid.Row="1" Grid.Column="1"/>
And CurrentControl is set to an instance of my UserControl:
private UserControl currentControl;
public UserControl CurrentControl
{
get { return currentControl; }
set
{
if (currentControl != value)
{
currentControl = value;
OnPropertyChanged("CurrentControl");
}
}
}
The source of the problem is the default value of your HeaderButtonsProperty - you set one using new PropertyMetadata(new ObservableCollection<ToggleButton>()). Contrary to what you expect it does not create one instance of the collection for each instance of your control, but a single instance shared across all of your controls.
Then you use this XAML syntax:
<controls:MyControl.HeaderButtons>
<ToggleButton (...) />
</controls:MyControl.HeaderButtons>
which does not assign a new collection to your HeadersButton property, but rather adds the specified item to the existing one. So each time this part of code is "executed", it adds a new copy of the ToggleButton to the single collection shared by all your controls.
To resolve the problem you should remove the default value from your HeaderButtonsProperty's metadata and assign a new collection instance in your control's constructor - that way each control instance will have it's own independent collection.

How to get WPF ListBox to update at start using ItemsSource?

I am very new to WPF and especially to data-binding but I'm trying to populate a ListBox with elements from an external resource, and trying to also follow the MVVM pattern. As such I am trying to avoid any code in my code-behind. I've looked over dozens of other questions similar to this but I feel I am missing something stupid as I cannot get my ListBox to generate with values. I have set the DataContext and then set the Binding for the ItemsSource to the correct property.
Question
How do I simply get this code to populate my empty ListBox when the application starts up?
XAML
<TabItem Name="ServerListTab" Header="Server List">
<TabItem.DataContext>
<viewModel:ServerListViewModel />
</TabItem.DataContext>
<ListBox
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding ServerList, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedServer}">
</ListBox>
</TabItem>
ServerList property in view model
public BindingList<string> ServerList
{
get { return _serverListModel.ServerList; }
set
{
if (ReferenceEquals(_serverListModel.ServerList, value)) return;
var aTestServers = //code hidden : gets array correctly from resource
for (var i = 0; i < aTestServers.Count; i++)
{
_serverListModel.ServerList.Add(aTestServers[i]);
}
InvokePropertyChanged("ServerList");
}
}

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!!

C# WPF Checkbox Databinding

I'm looking for the best way to populate a check boxes from the following code. I have looked into Binding but not really sure where to go.
Here is the edited code that is working
private void dpDateSelection_SelectedDateChanged(object sender, SelectionChangedEventArgs e)
{
DateTime? date = dpDateSelection.SelectedDate;
logDate = date != null ? date.Value.ToString("yyyy-MM-dd") : null;
dpDateSelection.ToolTip = logDate;
LoadLogs(logDate);
}
private void LoadLogs(string ldate)
{
string[] logs = Directory.GetFiles(logPath + ldate, "*.ininlog");
InitializeComponent();
logList = new ObservableCollection<String>();
logList.Clear();
foreach (string logName in logs)
{
string s = logName.Substring(logName.IndexOf(ldate) + ldate.Length + 1);
int extPos = s.LastIndexOf(".");
s = s.Substring(0, extPos);
logList.Add(s);
}
this.DataContext = this;
}
<ListBox x:Name="Logs" ItemsSource="{Binding logList}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" ToolTip="{Binding}" Tag="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You will want to start by using an ItemsControl instead of a StackPanel, since ItemsControls are automatically set up to display collections of things:
<ItemsControl ItemsSource="{Binding Logs}"/>
Note the use of ItemsSource. With the accompanying binding string, it basically says "Look for a property on the DataContext called "Logs" and put everything in it into this control".
Next you said you wanted this displayed as checkboxes, so we use an item template:
<ItemsControl ItemsSource="{Binding Logs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content={Binding .}/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This says "Use a checkbox for each Item in the ItemsSource". The DataTemplate can be a Grid or other collection control as well, so this is a really powerful feature in WPF. The "Binding ." just binds to the object itself (a string in this case, so we don't need a special path).
Finally, you need to set up a property to bind to in your view model:
ObservableCollection<String> Logs {get; set;}
You want an ObservableCollection so that when anything is added to or removed from the list, it automatically updates the UI. If you are going to be completely replacing the list (assignment), then you need to implement INotifyPropertyChanged and invoke the PropertyChanged event in that properties setter.
In your posted loop, you would add each log file to this property.
Also, make sure that somewhere you set the DataContext property of the XAML file (View) to your view model object. If everything is in code behind, use DataContext = this. Note that doing this is considered bad practice, and you should use a separate class (ViewModel).
You didn't mention what you wanted the CheckBoxes to do, so I haven't included anything related to that in my answer. You will likely want to abstract your logs into an object with a "Selected" property you can then bind the IsChecked property of the CheckBoxes to.
Obviously this is a lot to take in, please let me know if I can clarify anything or help further!
Update
You put the property in your ViewModel (DataContext). Whatever class that is, you write:
ObservableCollection<String> Logs {get; set;}
private void LoadLogs()
{
string[] logs = Directory.GetFiles(logPath + logDate, "*.ininlog");
foreach(string logName in logs)
{
string s = logName.Substring(logName.IndexOf(logDate) + logDate.Length + 1);
int extPos = s.LastIndexOf(".");
s = s.Substring(0, extPos);
//MessageBox.Show(s);
Logs.Add(s); //Add the parsed file name to the list
}
}

How to make this code more efficient?

As i am not very advanced in C# yet, I try to learn how to make my code more efficient.
I stored a lot of strings in some of the properties.
At the start of the application, i load all the seperatie properties into the textboxes.
I now ise this code to load them all:
private void LoadStoredStrings()
{
txtT1S1.Text = Properties.Settings.Default.strT1L1;
txtT1S2.Text = Properties.Settings.Default.strT1L2;
txtT1S3.Text = Properties.Settings.Default.strT1L3;
txtT1S4.Text = Properties.Settings.Default.strT1L4;
txtT1S5.Text = Properties.Settings.Default.strT1L5;
txtT1S6.Text = Properties.Settings.Default.strT1L6;
txtT1S7.Text = Properties.Settings.Default.strT1L7;
txtT1S8.Text = Properties.Settings.Default.strT1L8;
txtT1S9.Text = Properties.Settings.Default.strT1L9;
txtT1S10.Text = Properties.Settings.Default.strT1L10;
}
Obvious i can see the logic that each stored propertie ending with T1L1 also fits to the txt that ends with T1S1.
I just know this should be done in a more elegant and solid way than what i did now.
Could anyone push me in the right direction?
you can bind your properties directly to your textboxes
<UserControl xmlns:Properties="clr-namespace:MyProjectNamespace.Properties" >
<TextBox Text="{Binding Source={x:Static Properties:Settings.Default}, Path=strT1L1, Mode=TwoWay}" />
If you can get all of those constants into a List<string>, you could use it to bind to an ItemsControl with TextBlock inside:
Code behind or View Model
private ObservableCollection<string> _defaultProperties = new ObservableCollection<string>();
public ObservableCollection<string> DefaultProperties
{
get { return _defaultProperties; }
}
XAML
<ListBox ItemsSource="{Binding Path=DefaultProperties"}>
<ListBox.ItemTemplate>
<DataTemplate>
<!--Just saying "Binding" allows binding directly to the current data context vs. a property on the data context-->
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Categories

Resources