I have a MVVM WPF project where I have an devexpress accordian control which is populated with xml template items from a ViewModel. That works great, but my problem is when I click on one of the items in the accordian control and the selectedIndexChanged event is fired. I want to handle that in the MVVM manner and get the selected items value(which is a path to an xml file) from the accordian control, fetch the content of the xml file and databind a textbox control with the content of the xml file. The following is what I have tried so far.
Here is my xaml user control
<dxa:AccordionControl Grid.Column="0" x:Name="accordianTemplateMenu"
SelectionMode="Single" SelectionUnit="SubItemOrRootItem" ItemsSource="
{Binding TemplateItems}"
ChildrenPath="TemplateItems" DisplayMemberPath="Header >
<dxmvvm:Interaction.Behaviors>
<dxmvvm:EventToCommand EventName="SelectedItemChanged" Command="
{Binding EditCommand}">
<dxmvvm:EventToCommand.EventArgsConverter>
<Common:AccordionEventArgsConverter/>
</dxmvvm:EventToCommand.EventArgsConverter>
</dxmvvm:EventToCommand>
</dxmvvm:Interaction.Behaviors>
</dxa:AccordionControl>
<GridSplitter Grid.Column="1" />
<TextBlock Grid.Column="2" x:Name="templateItemContainer">
<Run Name="run" Text="{Binding XML}" ></Run>
</TextBlock>
This boils down to the AccordionEventArgsConverter which gets me the event arguments from the selecteditem in the accordian control:
public class AccordionEventArgsConverter :
EventArgsConverterBase<AccordionSelectedItemChangedEventArgs>
{
protected override object Convert(object sender,
AccordionSelectedItemChangedEventArgs args)
{
if (args != null)
{
return args;
}
return null;
}
}
And finally my viewmodel:
class TemplateMenuViewModel
{
private List<TemplateItem> _templateItems;
public TemplateMenuViewModel()
{
EditCommand = new DelegateCommand<object>(Edit, CanEdit);
}
public List<TemplateItem> TemplateItems
{
get
{
TemplateProvider provider = new TemplateProvider();
return provider.GetTemplateMenuItems("pathToMenuItems");
}
set { _templateItems = value; }
}
public ICommand<object> EditCommand { get; private set; }
public void Edit(object accordianItemArgs)
{
}
public bool CanEdit(object accordianItemArgs)
{
return accordianItemArgs != null;
}
}
I am able to get into the public void Edit method, which is great because from there I can use the accordianItemArgs to get the xml content, but how do I "return"/databind the xml content to the textblock element in the xaml file?
There are a couple of things:
You need the TemplateMenuViewModel to define an XML property. It looks like your TextBlock is already binding to it.
Then you need your ViewModel to implement the INotifyPropertyChanged interface. It doesn't look like you're doing that, then raise a property changed event when the XML text is set.
You should set your Text="{Binding XML}" with a Mode of OneWay:
Text="{Binding XML, Mode=OneWay}"
If you need more information on how to implement INotifyPropertyChanged, check out this tutorial: https://www.tutorialspoint.com/mvvm/mvvm_first_application.htm.
Related
I am working on an application showing 20 graphic buttons controls in a MainWindow (Button1 to Button20).
Each button control can display a Content string, and has a tooltip designed as follow :
<Button x:Name="button1" FontWeight="Bold" FontSize="15" Content="" HorizontalAlignment="Left" Margin="20,69,0,0" VerticalAlignment="Top" Width="92" Height="29" Click="Button_Click" Background="#FFFFFFFF" MouseEnter="button_MouseEnter">
<Button.ToolTip>
<Border Margin="-4,0,-4,-3" Padding="10" Background="Yellow">
<Border.BitmapEffect>
<OuterGlowBitmapEffect></OuterGlowBitmapEffect>
</Border.BitmapEffect>
<Label x:Name ="lbl1" FontSize="20" Content="{Binding Path=ToolTip}">
</Label>
</Border>
</Button.ToolTip>
<Button.Effect>
<DropShadowEffect/>
</Button.Effect>
</Button>
I would like to define the string content and the tooltip string for each button in an XML file so this information can be changed by modifying the XML file.
For this, I created a ViewModel defining an object called Bouton (in french) :
public class Bouton : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void Notify(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
int boutonNumber;
public int BoutonNumber{ get { return boutonNumber; } set { boutonNumber= value; Notify("BoutonNumber"); } }
string texteBouton;
public string TexteBouton { get { return texteBouton; } set { texteBouton = value; Notify("TexteBouton"); } }
string tooltip;
public string Tooltip { get { return tooltip; } set { tooltip = value; Notify("ToolTip"); } }
public Bouton(int nb, string tb, string tt)
{
BoutonNumber = nb;
TexteBouton = tb;
Tooltip = tt;
}
}
When reading the XML file, I create 20 objects of Bouton type with the information about boutonNumber, Content and Tooltip. Then all these Bouton objects are stored into a List collection.
Now I want to use DataBinding between my Bouton list and the graphic controls on my MainWindow to be able to display the content string and the tooltip string on each button.
In the MainWindow, I used the following code :
public MainWindow()
{
InitializeComponent();
List<Bouton> lst = Utilities.CreateList();
this.DataContext = lst;
}
where lst is a List collection correctly initialized.
But I do not know how to make the databinding work on the Button controls. How can I make each of the 20 Button controls correctly link to the corresponding Bouton object (contained in the Boutons collection) ? I mean how can Button1 control get its strings from my Bouton1 object, the Button2 control get its string from Bouton2 object and so on until Button20 control and Bouton20 object ?
Thank you for your help. Please note I am a beginner with WPF and this is my first WPF project with Visual Studio.
I think the simplest option would be to wrap your button in a UserControl.
This UserControl then contains a property of type Bouton (not great naming btw. - even if it is a different language, the term "button" already exists, so this is rather ambiguous. As a non-native english speaker myself, I also recommend getting used to naming in English, it will save you lots of headaches in the long run, but that might be subjective )
You can then bind directly to that property in the UserControl's template. All you have to do is assign each UserControl the correct button data.
eg.
public class CustomButton : UserControl
{
public Bouton ParsedButtonData { get; set; }
public CustomButton()
{
DataContext = this;
InitializeComponent();
}
}
and in your UserControl's template:
<Button ...>
<Button.ToolTip>
<Border ...>
<Label Content="{Binding ParsedButtonData.Tooltip}" ...>
</Label>
</Border>
</Button.ToolTip>
</Button>
You can then place the UserControl in your XAML like this:
xmlns:ctrls="clr-namespace:MyProject.MyNamespace"
<ctrls:CustomButton name="myFirstButton"/>
Now all you have to do is make sure each CustomButton has their ParsedButtonData set to the corresponding piece of data. You can either set this manually for each button created in your XAML, or you can create the CustomButtons through C# in the first place.
If you create your UserControls in XAML, for example:
public void SomeMethod()
{
myFirstButton.ParsedButtonData = lst[0];
}
Alternatively, you might want to look into extending ItemsControl. It's basically made for this sort of application. An ItemsControl has an ItemsSource, which can be any collection type such as List<> or ObservableCollection<>. It then creates its children from that collection, setting the DataContext automatically to the corresponding element in said list.
Examples of that would be DataGrid or ListView. I find that to be a little more involved though, if you really just want to place a bunch of a Buttons on a single View.
I am trying to implement WPF Combobox Autocomplete TextSearch like "Contains" instead of "Start with".
Couple of question threads are there but could not find any concrete solution.
I was following the answer by #Evgenii:
WPF combobox textsearch with contains
In the SetText(DependencyObject element, string text) method, the value of "text" parameter is always a "DeviceNumber" string. So my text is not reflecting there.
Here is my own sample code
https://drive.google.com/open?id=1eqK5bh5SQJPxHeb-zzOuBHIpYapv-h18
Any reason?
Is anyone successfully implemented Text Search with Contains?
Please guide.
I thank you for every answer I get but working code is much appreciable :)
i recommend using AutoCompleteBox, it 's just like ComboBox, it has ItemsSource and SelectedItem and all like ComboBox
you can use it property 'AutoCompleteBox.FilterMode' which take AutoCompleteFilterMode enumeration, the enumerations include:Contains, ContainsCaseSensitive, ContainsOrdinal
and other helpful ...
here is how you use it:
https://www.broculos.net/2014/04/wpf-autocompletebox-autocomplete-text.html
and here it an example of using filter mode:
https://learn.microsoft.com/en-us/previous-versions/windows/silverlight/dotnet-windows-silverlight/dd833103(v=vs.95)?redirectedfrom=MSDN
make custom combobox control.
public class SearchComboBox : ComboBox
{
TextBox editableTextBox;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
editableTextBox = GetTemplateChild("PART_EditableTextBox") as TextBox;
editableTextBox.TextChanged += EditableTextBox_TextChanged;
}
private void EditableTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
ICollectionView ICV = ItemsSource as ICollectionView;
if(ICV != null)
{
if (string.IsNullOrEmpty(editableTextBox.Text.Trim()))
ICV.Filter = null;
else
ICV.Filter = new Predicate<object>(i => ((Equipment)i).equipmentLabel.Contains(editableTextBox.Text));
IsDropDownOpen = true;
}
}
}
modify you EquipmentScreenViewModel Code. add ICollectionView type property
public class EquipmentScreenViewModel
{
public string SelectedEquipmentRego { get; set; }
public ObservableCollection<Equipment> AllEquipments { get; set; }
private ICollectionView _allEquipCollection = null;
public ICollectionView AllEquipCollection
{
get
{
if (_allEquipCollection == null && AllEquipments != null)
{
_allEquipCollection = CollectionViewSource.GetDefaultView(AllEquipments);
}
return _allEquipCollection;
}
}
}
XAML
<Grid>
<local:SearchComboBox x:Name="cmbAlternativeAsset"
Width="200" IsEditable="True"
FontSize="12" Foreground="#494949"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
SelectedItem="{Binding SelectedEquipmentRego, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding AllEquipCollection}" SelectedValuePath="equipmentRego"
DisplayMemberPath="equipmentLabel" IsTextSearchEnabled="False"
/>
</Grid>
Binding ItemsSource to CollectionView and IsTextSearchEnabled false. Good Luck
So I am trying to bind the following ViewModel:
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<ListBoxItem> _PlacesOrCities;
public ObservableCollection<ListBoxItem> PlacesOrCities
{
get { return _PlacesOrCities; }
set { _PlacesOrCities = value; RaisePropertyChanged("PlacesOrCities"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ViewModel()
{
_PlacesOrCities = new ObservableCollection<ListBoxItem>();
}
}
To the following xaml:
<ListBox Name="lbPlacesCity" ItemsSource="{Binding Path=(gms:MainWindow.ViewModel).PlacesOrCities, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate DataType="models:ListBoxItem">
<TextBlock Style="{StaticResource MaterialDesignBody2TextBlock}" Text="{Binding Name}" Visibility="{Binding Visibility}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In the codebehind as such:
public ViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new ViewModel();
DataContext = ViewModel;
}
And upon firing a button click event- I try to set the values of the observable collection using a in memory list:
private void StateProvince_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
_CurrentSelectionPlaces = Canada.Provinces
.FirstOrDefault(x => x.Abbreviation == _SelectedStateProvince_ShortName)
.Place.OrderBy(x => x.Name).ToList();
foreach (var currentSelectionPlace in _CurrentSelectionPlaces)
{
ViewModel.PlacesOrCities.Add(currentSelectionPlace);
}
}
But it seems like none of the items are being added to the collection. Am I binding it incorrectly?
I've tried quite a few solutions but none of them seem to change the result- where no items in the list are being loaded into the collection properly.
EDIT:
It may be worth noting that the ListBoxItem as seen in the ViewModel is a custom model:
public class ListBoxItem
{
[J("Name")] public string Name { get; set; }
[J("PostalCodes")] public string[] PostalCodes { get; set; }
public Visibility Visibility { get; set; } = Visibility.Visible;
}
You should try to fit to the MVVM pattern, so the population of the list should occur at viewmodel level and not in the view's code behind.
You mentioned that you use a click event, instead of doing so, try to bind the command property of the button to a command in the viewmodel, see this link with an explanation of several types of commands and how to use them: https://msdn.microsoft.com/en-us/magazine/dn237302.aspx
In the other hand, if you already set the data context in the window constructor, to bind the ListBox items source you only need the name of the property to bind, "PlacesOrCities":
<ListBox Name="lbPlacesCity" ItemsSource="{Binding Path=PlacesOrCities, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate DataType="models:ListBoxItem">
<TextBlock Style="{StaticResource MaterialDesignBody2TextBlock}" Text="{Binding Name}" Visibility="{Binding Visibility}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It would also be recommendable trying to load the items in the list without any template, you can use ListBox DisplayMemberPath property to display the name, and once you are able to load items, apply the style.
Also in the way you use ObservableCollection, you actually need to replace the whole collection instead of adding to fire RaisePropertyChanged, try a normal property instead.
public ObservableCollection<ListBoxItem> PlacesOrCities {get;set;} = new ObservableCollection<ListBoxItem>();
Modifying the collection will update the UI, so whenever you use Add or Clear, the UI should know it.
Hope it helps.
I have been working on as assignment where WPF GUI is genereated at runtime. Runtime generated GUI is consumed by another wpf application. Template generator application allows to create GUI and save it as xml (xaml is actually xml) using XAMLWriter. Consumer application use XAMLReader to add tamplate on GUI. Now i want some sort of binding among controls in generated template.
Requirement : Date on first Datepicker = 2015/01/02 and textbox Text = 1 then date on second datepicker must be 2015/01/03. If textbox text = -1 the date on second date picker must have 2015/01/01.
How i could achieve this at runtime. Nothing needs to hard code as generated template is generated from another application. We have some specific values on Tag property of control which indicates us which three controls are involved and which datepicker is source ,which datepicker is destination and which textbox text needs to used.
Is it possible to use Dynamic data binding? or how this can be accomplished
=> Rename your Xml file to Xaml
<UserControl ...>
<Grid>
<StackPanel Background="Aqua">
<TextBlock Text="{Binding Path=Label}" Width="200" Height="40"/>
</StackPanel>
</Grid>
=> This is the class you will be merging with the code-behind if there is
public class XamlLoadedType:UserControl, IComponentConnector
{
private bool _contentLoaded;
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
var resourceLocater = new System.Uri(_uri, System.UriKind.Relative);
Application.LoadComponent(this, resourceLocater);
}
void IComponentConnector.Connect(int connectionId, object target) {
this._contentLoaded = true;
}
string _uri ;
public XamlLoadedType(string uri)
{
_uri = uri;
InitializeComponent();
}
}
=> The main window and it's viewmodel:
<Window ...>
<Grid>
<StackPanel>
<Button Command="{Binding LoadCommand}" Width="100" Height="50">Load</Button>
</StackPanel>
<StackPanel Grid.Row="1">
<ContentControl Content="{Binding LoadedContent}"/>
</StackPanel>
</Grid>
public class MainViewModel:INotifyPropertyChanged
{
public ICommand LoadCommand { get; set; }
object _loadedContent;
public object LoadedContent
{
get { return _loadedContent; }
set {
SetField(ref _loadedContent, value, "LoadedContent");
}
}
public MainViewModel()
{
LoadCommand = new RelayCommand(Load, ()=>true);
}
private void Load()
{
var xamlLoaded = new XamlLoadedType("/WPFApplication1;component/XamlToLoad.xml.xaml");
xamlLoaded.DataContext = new { Label = "HeyDude" };
LoadedContent = xamlLoaded;
}
}
VoilĂ
I got a sample mvvm app. The UI has a textbox, a button and a combobox. when I enter something in the textbox and hit the button, the text I enter gets added to an observablecollection. The Combobox is bound to that collection. How do I get the combobox to display the newly added string automaticly?
As I understand correctly, you want to add an item and select it.
Here is the example how it can be done using ViewModel and bindings.
Xaml:
<StackPanel>
<TextBox Text="{Binding ItemToAdd}"/>
<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
<Button Content="Add" Click="Button_Click"/>
</StackPanel>
ViewModel:
public class MainViewModel:INotifyPropertyChanged
{
public ObservableCollection<string> Items { get; set; }
public string ItemToAdd { get; set; }
private string selectedItem;
public string SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
public void AddNewItem()
{
this.Items.Add(this.ItemToAdd);
this.SelectedItem = this.ItemToAdd;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The MainViewModel has 3 properties (one for the TextBox and two other for the ComboBox) and the method AddNewItem without parameters.
The method can be triggered from a command, but there is no standard class for commands, so I will call it from the code-behind:
((MainViewModel)this.DataContext).AddNewItem();
So you must explicitly set an added item as selected after you add it to a collection.
Because the method OnItemsChanged of the ComboBox class is protected and can't be used.
If the ComboBox is bound to an ObservableCollection, the ComboBox will be updated as soon as the collection is changed.
That's the advantage of using an ObservableCollection - you don't need to do any extra coding to update the UI.
If this is not the behavior you're seeing, perhaps you can post some code/xaml.