Wenn du ein DataTable als Source verwendest, wovon ich ausgehe, dann sind die Einträge im Dropdown DataRowView Einträge. Damit könntest du vermutlich so vorgehen:
private void CbRole_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (cbRole.SelectedItem is DataRowView row)
{
var id = row["ID"]?.ToString(); // oder: row.Row["ID"]
var description = row["Description"]?.ToString();
MessageBox.Show($"ID: {id}, Description: {description}");
}
}
Falls du SelectedItem wirklich binden möchtest, brauchen wir sicher ein paar Zusatzinformationen zu der Eigenschaft "ID" die gebunden werden soll. So wie bisher wird das nicht funktionieren:
SelectedItem="{Binding ID, Mode=TwoWay}" SelectedValue="{Binding ID}" SelectedValuePath="{Binding ID}"
Wie hier so oft gepredigt wird, solltest du dem WPF Konzept folgen und mit ViewModels arbeiten. Da ich noch sehr gut weiß, wie verwirrend der Einstieg sein kein, habe ich mir die Zeit genommen, dir ein kleines schnelles Grundgerüst für "RezepteBearbeiten" zu bauen. Möglicherweise erkennst du die Vorteile gegenüber deinem momentanen Stil.
In deinem Code ist bisher niemand zuständig:
<TextBlock Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" DockPanel.Dock="Bottom" Padding="5,5,5,1"
Width="{TemplateBinding Width}" TextAlignment="Left" FontWeight="Bold"
Text="{TemplateBinding Content, Converter={StaticResource SplitKonv}, ConverterParameter=1}" />
<!-- Resizable Thumb -->
<Thumb Grid.Row="1" Grid.Column="2" Width="5" Cursor="SizeWE"
HorizontalAlignment="Right" Background="Transparent"
DragDelta="Thumb_DragDelta"/>
private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
{
if (sender is Thumb thumb && thumb.TemplatedParent is GridViewColumnHeader header)
{
if (header.Column != null)
{
double newWidth = header.Column.Width + e.HorizontalChange;
header.Column.Width = Math.Max(newWidth, 20); // Mindestbreite setzen
}
}
}
Moin,
ich gehe üblicherweise den Weg, die Eingabe über einen zusätzlichen Dialog anzubieten. Möglicherweise ist das auch eine Alternative für dich.
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Style="{StaticResource FormTextBlockStyle}" Text="{Binding Note}" TextAlignment="Left" HorizontalAlignment="Left"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DockPanel LastChildFill="True">
<Button Content="Bearbeiten" Margin="0,0,4,0" Command="{Binding EditNoteCommand}"/>
<TextBlock HorizontalAlignment="Left" TextAlignment="Left" VerticalAlignment="Center" Style="{StaticResource FormTextBlockStyle}" Text="{Binding Note}" />
</DockPanel>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
Solltest du bei deinem Vorhaben bleiben wollen, musst du dich ein wenig mit den DataGridCell - Events und dem Stichwort "Eingabefokus" beschäftigen.
<Popup
IsOpen="{Binding IsSelected, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}}"
Placement="MousePoint"
StaysOpen="False">
<TextBox
Width="300"
Height="200"
AcceptsReturn="True"
Text="{Binding Note}" />
Das würde mich ebenfalls interessieren, passierte öfter in den letzten Wochen. Lösung bisher war das Löschen der [obj] und [bin] Ordner mit anschließender Projekt Neuerstellung.
Schau dir die GroupName Property an.
Ich verstehe nicht ganz was du vorhast, aber vermutlich dies:
string name = null;
string pass =null;
foreach (DataRow datarow in table.Rows)
{
name= datarow["Benutzername"].ToString();
pass = datarow["Passwort"].ToString();
if (name.Contains(Txt_Benutzername.Text) && pass.Contains(Txt_Passwort.Text))
{
MessageBox.Show("Klappt");
}
else
{
MessageBox.Show("Klappt nicht");
}
}
Nicht einfach aufgeben 🙂
Die DataGrid Eigenschaft AutoGenerateColumns sollte das erste Problem lösen.
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding ProjektUmsatzTabelle}"/>
Diese kannst du natürlich auch so setzen:
Dtg_Umsatzvorschau.AutoGenerateColumns=false;
Dtg_Umsatzvorschau.ItemsSource = ProjektUmsatzTabelle;
Formatierung:
Standard numeric format strings
Wenn du das erledigt hast, ist dies eine Variante für das nächste Problem:
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</DataGrid.ColumnHeaderStyle>
Du solltest neben all dem aber versuchen zu verstehen, warum deine Bindung in XAML nicht klappt, die im Codebehind jedoch schon.
<DataGrid ItemsSource="{Binding Path=TestViewModel.ProjektUmsatzTabelle}"/>
Du bindest damit bereits an die Auflistung deines Typs "Jahrestabelle".
Analog zu Th69´s Vorgehen sollte es dann so in etwa gehen:
...
Spalte = new DataGridTextColumn() { Header = "Kunde", Binding = new Binding("Kunde") };
Spalte = new DataGridTextColumn() { Header = "Projekt", Binding = new Binding("Projekt") };
...
for (int j = ProgStartJahr; j ≤ ProgEndJahr; j++)
{
Spalte = new DataGridTextColumn()
{
Header = j,
Binding = new Binding(string.Format("[{0}].Volumen", j - ProgStartJahr))
};
Dgd_Umsatzvorschau.Columns.Add(Spalte);
}
Ich verstehe immer noch nicht ganz wie das aussehen soll, aber mein erster Gedanke ging in diese Richtung (vor meinem eigentlichen Job schnell getippt 🙂
public class JahresTabelle : ObservableCollection<VolumeDetail>
{
public JahresTabelle(string kunde, string projectName)
{
Kunde = kunde;
ProjektName = projectName;
}
public string Kunde { get; set; }
public string ProjektName { get; set; }
}
public class VolumeDetail
{
public VolumeDetail(int jahr, double[] volumen)
{
Jahr = jahr;
Volumen = volumen.ToList();
Summe = Volumen == null ? 0 : Summe = Volumen.Sum();
}
public int Jahr
{ get; private set; }
public List<double> Volumen
{ get; private set; }
public double Summe
{ get; private set; }
}
Random random = new Random();
var projektBezeichnungsTabelle = new List<JahresTabelle>();
var progStartJahr = 2022;
var progEndJahr = 2030;
//erstellt 50 Kunden mit einem Projekt
for (int tempID = 0; tempID < 50; tempID++)
{
var data = new JahresTabelle("Kunde" + tempID, "Projekt" + tempID);
for (int jahr = progStartJahr; jahr < progEndJahr; jahr++)
{
var volumesOfYear = new double[]
{
random.NextDouble(),
random.NextDouble(),
random.NextDouble(),
};
data.Add(new VolumeDetail(jahr, volumesOfYear));
}
projektBezeichnungsTabelle.Add(data);
}
//create columns
DataGridTextColumn column;
var columnCollection = new List<DataGridTextColumn>();
{
//kunde
column = new DataGridTextColumn()
{
Header = "Kunde",
Binding = new Binding("Kunde")
};
columnCollection.Add(column);
//projekt
column = new DataGridTextColumn()
{
Header = "ProjektName",
Binding = new Binding("ProjektName")
};
columnCollection.Add(column);
//dynamic year columns
for (int j = progStartJahr; j < progEndJahr; j++)
{
column = new DataGridTextColumn()
{
Header = "Vol. im Jahr " + j,
Binding = new Binding(string.Format("[{0}].Jahr.Summe", j - progStartJahr))
};
columnCollection.Add(column);
}
Mir ist nicht ganz klar, wie das Ergebnis aussehen soll oder darf:
Kunde 1 | Projekt 1 | [44,5] [2,3] [0,8]
Kunde 1 | Projekt 2 | [2,3] [4,5] [9,8] [3,3]
Kunde 2 | Projekt 1 | [9,8] [2,5]
oder eher so?
Kunde 1 | Projekt 1 | 44,5
Kunde 1 | Projekt 1 | 2,3
Kunde 1 | Projekt 1 | 0,8
Kunde 1 | Projekt 2 | 2,3
Kunde 2 | Projekt 2 | 4,5
Kunde 2 | Projekt 2 | 9,8
Kunde 2 | Projekt 2 | 3,3
usw...
Guten Abend,
ich suche nach einem Lösungsansatz. Ich möchte eine Arbeitswoche über 7 Spalten darstellen, welche initial je 10 Zeilen für ein Employee - Drop bereitstellt (siehe Bild im Anhang). Aktuell habe ich mich hier für ein DataGrid als Basis entschieden und versuche dies entsprechend meiner Ideen zu modifizieren. Hierbei suche ich nach für mich attraktiven Varianten ein Modell zu entwickeln, welches "einfach" gebunden und via ViewModel modifiziert werden kann. Mein aktueller Ansatz sieht aktuell so aus, erscheint mir aber unnötig kompliziert. Habt ihr andere Ansätze?
public class MatrixSource : ObservableCollection<MatrixRow>
{
public MatrixSource(int year, int week, string workingArea, ShiftEnum shift, int initialRowCount = 10)
{
WeekOfYear = year;
Week = week;
WorkingArea = workingArea;
Shift = shift;
DateRange = DateProvider.Get_WeekDateRangeOf(week, year);
Build(DateRange, initialRowCount);
}
public List<MatrixColumn> ColumnInfoList
{ get; private set; }
private void
Build(CalendarDateRange dateRange, int initialRowCount)
{
#region create columninfos
ColumnInfoList = new List<MatrixColumn>();
var startDate = dateRange.Start.Date;
while(startDate < dateRange.End.Date.AddDays(1))
{
var columnHeader = new ShiftHeaderData(new DateInfo(startDate), ShiftEnum.Unbekannt, string.Empty, string.Empty);
var columnInfo
= new MatrixColumn(this, ColumnInfoList.Count, columnHeader);
ColumnInfoList.Add(columnInfo);
startDate = startDate.AddDays(1);
}
#endregion
#region create default rows
for (int rowIndex = 0; rowIndex < 10; rowIndex++)
{
var row = new MatrixRow(this, rowIndex, ColumnInfoList.Count);
row.OnRowDataChanged += Row_DataChanged;
Add(row);
}
#endregion
}
public class MatrixColumn : BaseNotify
{
public MatrixColumn(MatrixSource parent, int displayIndex, ShiftHeaderData headerData)
{
ParentMatrix= parent;
DisplayIndex = displayIndex;
HeaderData = headerData;
}
#region properties
public MatrixSource ParentMatrix
{ get; private set; }
public ShiftColumn ParentColumn
{ get; set; }
public int DisplayIndex
{ get; private set; }
public ShiftHeaderData HeaderData
{ get; private set; }
#endregion
}
public class MatrixRow : ObservableCollection<EmployeeCellData> , INotifyPropertyChanged
{
public MatrixRow(MatrixSource parentMatrix, int displayIndex, int defaultCellCount = 7)
{
ParentMatrix = parentMatrix;
DisplayIndex = displayIndex;
for (int cellIndex = 0; cellIndex < defaultCellCount; cellIndex++)
{
var cell = new EmployeeCellData(parentMatrix, this);
cell.OnDataChanged += Cell_OnDataChanged;
Add(cell);
}
}
}
In XAML binde ich so:
<local:ShiftDataGrid x:Name="DebugDG" Grid.Column="1" Grid.Row="1" MinRowHeight="30"
GenerateDefaultMatrixSource="True"
MatrixSource="{Binding Path=MatrixSource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
Damit mein DataGrid damit auch was anfangen kann:
public class ShiftDataGrid : DataGrid, INotifyPropertyChanged
{
public ShiftDataGrid()
{
InitMe();
}
#region events
public event PropertyChangedEventHandler PropertyChanged = delegate { };
#endregion
#region properties
public static readonly DependencyProperty MatrixSourceProperty = DependencyProperty.Register("MatrixSource", typeof(MatrixSource), typeof(ShiftDataGrid), new PropertyMetadata(OnMatrixSourceChanged));
public MatrixSource MatrixSource
{
get { return (MatrixSource)GetValue(MatrixSourceProperty); }
set
{
SetValue(MatrixSourceProperty, value);
}
}
public static readonly DependencyProperty GenerateDefaultMatrixSourceProperty = DependencyProperty.Register("GenerateDefaultMatrixSource", typeof(bool), typeof(ShiftDataGrid), new PropertyMetadata(false));
public bool GenerateDefaultMatrixSource
{
get { return (bool)GetValue(GenerateDefaultMatrixSourceProperty); }
set
{
SetValue(GenerateDefaultMatrixSourceProperty, value);
}
}
private void
Build_Matrix(MatrixSource matrixData)
{
IsBusy = true;
{
SelectedCells?.Clear();
SelectedLastEmployeeCell = null;
Columns?.Clear();
if (matrixData != null)
{
foreach (var columnInfo in matrixData.ColumnInfoList)
{
columnInfo.ParentColumn
= new ShiftColumn(columnInfo.HeaderData);
Columns.Add(columnInfo.ParentColumn);
}
ItemsSource = matrixData;
}
}
IsBusy = false;
}
private static void
OnMatrixSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
(sender as ShiftDataGrid).Build_Matrix(e.NewValue as MatrixSource);
}
Und eine entsprechende DataGridColumn:
public class ShiftColumn : DataGridBoundColumn
{
public ShiftColumn(ShiftHeaderData headerData)
{
this.Header = headerData;
}
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
EmployeeCellPresenter presenter = new EmployeeCellPresenter() //<----- UserControl als Drag&Drop target/sourc
{
IsDragEnabled = true,
IsDragIndicatorEnabled = true,
IsDropEnabled = true,
IsDropIndicatorEnabled = true
};
Binding binding;
#region bind DataContext
binding = new Binding();
{
binding.Source = cell;
binding.Mode = BindingMode.TwoWay;
binding.Path = new PropertyPath("DataContext");
}
BindingOperations.SetBinding(presenter, EmployeeCellPresenter.DataContextProperty, binding);
#endregion
#region bind cell data
binding = new Binding();
{
binding.Source = cell;
binding.Mode = BindingMode.TwoWay;
binding.Path = new PropertyPath("DataContext.[" + cell.Column.DisplayIndex + "]");
}
BindingOperations.SetBinding(presenter, EmployeeCellPresenter.CellDataProperty, binding);
#endregion
...
public class EmployeeCellData : BaseNotify
{
public EmployeeModel Data { get; set; }
...
}
Aus deinem switch (bool) solltest du ein if () else machen.
Und ich würde die View erst erzeugen und dann zuweisen:
@Abt
@Th69
2. Das wollte ich wissen und wirkt unter deinem Beispiel dann auch gut erklärt.
Aber irgendwie wußte ich wohl intuitiv, daß etwas an der View-Erzeugung nicht stimmt...
Darum hab ich mich an euch gewendet. Danke nochmal.
Ok, das war mir auch noch nicht bekannt.
The problem there is that CollectionViewSource.GetDefaultView(object) will always return the same ICollectionView instance for a given source, and this is what any ItemsControl extension will use when displaying that source.
Diese Änderung brachte den Erfolg:
//var view = CollectionViewSource.GetDefaultView(uc.Source);
var view = new CollectionViewSource { Source = uc.Source }.View;
Danke für eure Unterstützung.
Guten Morgen,
Deine Hinweise habe ich umgesetzt, obwohl ich beim 2ten nicht
wüsste warum das sinnvoller ist !?
Aus deinem switch (bool) solltest du ein if () else machen.
Und ich würde die View erst erzeugen und dann zuweisen:
Zu meinem Kernproblem bleibt der Aha Effekt noch immer aus.
Der Reihe nach aktiviert:
View Teamleiter: Application, Distributor, Setup
View Debug: Application, Distributor, Setup
USER @FilterFunction: Filter: 0
TEAMLEITER @FilterFunction: Filter: 0
DEBUG @FilterFunction: Filter: 0
DEBUG @FilterFunction: Filter: 0
DEBUG @FilterFunction: Filter: 0
TEAMLEITER @FilterChanges Filter: Application
TEAMLEITER @CommandExecute Filter: Application
DEBUG @FilterFunction: Filter: 0
DEBUG @FilterFunction: Filter: 0
DEBUG @FilterFunction: Filter: 0
TEAMLEITER @FilterChanges Filter: Application, Distributor
TEAMLEITER @CommandExecute Filter: Application, Distributor
DEBUG @FilterFunction: Filter: 0
DEBUG @FilterFunction: Filter: 0
DEBUG @FilterFunction: Filter: 0
TEAMLEITER @FilterChanges Filter: Application, Distributor, Setup
TEAMLEITER @CommandExecute Filter: Application, Distributor, Setup
DEBUG @FilterFunction: Filter: 0
DEBUG @FilterFunction: Filter: 0
DEBUG @FilterFunction: Filter: 0
DEBUG @FilterChanges Filter: Application
DEBUG @CommandExecute Filter: Application
DEBUG @FilterFunction: Filter: Application
DEBUG @FilterFunction: Filter: Application
DEBUG @FilterFunction: Filter: Application
DEBUG @FilterChanges Filter: Application, Distributor
DEBUG @CommandExecute Filter: Application, Distributor
DEBUG @FilterFunction: Filter: Application, Distributor
DEBUG @FilterFunction: Filter: Application, Distributor
DEBUG @FilterFunction: Filter: Application, Distributor
DEBUG @FilterChanges Filter: Application, Distributor, Setup
DEBUG @CommandExecute Filter: Application, Distributor, Setup
DEBUG @FilterFunction: Filter: Application, Distributor, Setup
DEBUG @FilterFunction: Filter: Application, Distributor, Setup
DEBUG @FilterFunction: Filter: Application, Distributor, Setup
OK, Danke Th69. Das war dann wohl der entscheidende Hinweis.
<local:UC_Message Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Title="MELDUNGEN"
Source="{Binding Path=Data.MessageViewModel.Messages, Source={StaticResource MainViewModel}, UpdateSourceTrigger=PropertyChanged}"
Margin="5,0,5,5"/>
Dies hatte ich wie gesagt in insgesamt mehreren SubViews so integriert. Nachdem ich nun alle
Source - Bindings bis auf einen auskommentiert habe, funktionierts. Ich weiß nur noch
nicht warum... Weil die zugrundeliegende Source - Auflistung im MessageViewModel die selbe ist?
Da UC_Message selbst die View ist und ich diese wie üblich instanziiere, glaube ich das gleiche Objekt zu nutzen, ja.
<local:UC_Message Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Title="MELDUNGEN"
Source="{Binding Path=Data.MessageViewModel.Messages, Source={StaticResource MainViewModel}, UpdateSourceTrigger=PropertyChanged}"
Margin="5,0,5,5"/>
Evtl. noch interessant. Zeigt immer korrekt die gerade
gesetzten Filter an.
<TextBlock DockPanel.Dock="Right" Foreground="{StaticResource MyBlue}" Margin="20,0"
Text="{Binding Filter, ElementName=ThisUC,FallbackValue=noFilter, StringFormat={}Aktive Filter: {0}}" VerticalAlignment="Center"/>
Hier erfolgt nur die Zuweisung des Filter Predikats:
/* set view */
switch (uc.Source == null)
{
case true:
uc.View = null; break;
default:
{
uc.View = CollectionViewSource.GetDefaultView(uc.Source);
uc.View.Filter = uc.Filter_Function; //<------------------------------------------
break;
}
}
Über ein DataProvider werden die Enumerationswerte in eine Liste geholt, in einem Menu
dargestellt, angeklickt und dadurch FilterItemClickCommand aufgerufen.
<ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="MessageTypeProvider">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="helper:MessageTypeEnum" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<Button Content="Filter anpassen"...
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding Source={StaticResource MessageTypeProvider}}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MahApps.Styles.MenuItem}">
<Setter Property="StaysOpenOnClick" Value="True"/>
<Setter Property="Foreground" Value="{StaticResource MyBlue}"/>
<Setter Property="Header" Value="{Binding Converter={StaticResource GetEnumDescriptionConverter}}"/>
<Setter Property="IsCheckable" Value="true"/>
<Setter Property="Command" Value="{Binding Data.FilterItemClickCommand, Source={StaticResource ThisUCProxy}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
...
...
Filter_Function dürfte nur hier gezielt durch Refresh aufgerufen werden!?
_FilterItemClickCommand = new DelegateCommand<object>((s) =>
{
var _typeOfMessage = (MessageTypeEnum)s;
if (Filter.HasFlag(_typeOfMessage))
{
Filter &= ~_typeOfMessage; // remove it
}
else
{
Filter |= _typeOfMessage; // add it
}
Debug.WriteLine("active filter @CommandExecute: " + Filter);
View?.Refresh(); //<---------------------------------------------------------
},
(s) => { return true; });
Das MessageModel selbst sieht so aus:
public class Base_MessageModel
{
public Base_MessageModel(MessageTypeEnum _target, string _message)
{
Target = _target;
RawMessage = _message;
Date = DateTime.Now;
}
public Base_MessageModel(MessageTypeEnum _target, EndPoint _clientEndPoint, EndPoint _serverEndPoint, string _message)
{
Target = _target;
RawMessage = _message;
ClientEndPoint = _clientEndPoint;
ServerEndPoint = _serverEndPoint;
Date = DateTime.Now;
}
public MessageTypeEnum Target { get; private set; }
private string _RawMessage;
public string RawMessage
{
get { return _RawMessage; }
set
{
_RawMessage = value;
Message = Get_PrintableMessage(value);
}
}
public string Message { get; private set; }
...
...
Das kann ich soweit akzeptieren, hilft mir aber leider nicht beim Problem.
Hallo,
mir konkreter Titel ist mir nicht eingefallen.
Zu meinem Problem:
Mein MessageViewModel stellt eine Auflistung IEnumerable<Base_MessageModel> bereit.
Die MessageView dazu wird an mehreren Stellen im Programm verwendet und erstellt
aus der o.g. Quelle immer eine CollectionView:
<DataGrid ItemsSource="{Binding Path=View, ElementName=ThisUC, UpdateSourceTrigger=PropertyChanged}">
public partial class UC_Message : UserControl
{
public UC_Message()
{
InitializeComponent();
_FilterItemClickCommand = new DelegateCommand<object>((s) =>
{
var _typeOfMessage = (MessageTypeEnum)s;
switch (Filter.HasFlag(_typeOfMessage) == false)
{
case true:
Filter |= _typeOfMessage; break;
default:
Filter &= ~_typeOfMessage; break;
}
Debug.WriteLine("active filter @CommandExecute: " + Filter);
View?.Refresh();
},
(s) => { return true; });
}
public static readonly DependencyProperty ViewProperty = DependencyProperty.Register("View", typeof(ICollectionView), typeof(UC_Message));
public ICollectionView View
{
get { return (ICollectionView)GetValue(ViewProperty); }
set
{
SetValue(ViewProperty, value);
}
}
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(IEnumerable<Base_MessageModel>), typeof(UC_Message), new PropertyMetadata(OnSourceChanged));
public IEnumerable<Base_MessageModel> Source
{
get { return (IEnumerable<Base_MessageModel>)GetValue(SourceProperty); }
set
{
SetValue(SourceProperty, value);
}
}
private static void
OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var uc = (UC_Message)sender;
/* set view */
switch (uc.Source == null)
{
case true:
uc.View = null; break;
default:
{
uc.View = CollectionViewSource.GetDefaultView(uc.Source);
uc.View.Filter = uc.Filter_Function;
break;
}
}
}
Ziel ist, dass jede Instanz der View in der Lage ist die gleiche Datenbasis bei Bedarf unterschiedlich zu filtern. Dazu hier noch etwas um dies zu können:
public static readonly DependencyProperty Filter_IsEnabledProperty = DependencyProperty.Register("Filter_IsEnabled", typeof(bool), typeof(UC_Message), new PropertyMetadata(true));
public bool Filter_IsEnabled
{
get { return (bool)GetValue(Filter_IsEnabledProperty); }
set
{
SetValue(Filter_IsEnabledProperty, value);
}
}
public static readonly DependencyProperty FilterProperty = DependencyProperty.Register("Filter", typeof(MessageTypeEnum), typeof(UC_Message), new PropertyMetadata(Filter_Changed));
public MessageTypeEnum Filter
{
get { return (MessageTypeEnum)GetValue(FilterProperty); }
set
{
SetValue(FilterProperty, value);
}
}
private static void
Filter_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var uc = (UC_Message)sender;
Debug.WriteLine("active filter @FilterChanged: " + uc.Filter); // <---------------- nur zum prüfen was hier los ist
}
Die Filter Funktion sieht so aus und vermutlich liegt hier das Problem. Debug.WriteLine gibt
hier stets active filter @FilterFunction: 0 zurück was ich mir beim besten Willen einfach
nicht mehr erklären kann.
private bool
Filter_Function(object messageObject)
{
if (Filter_IsEnabled == false)
return true;
var message = messageObject as Base_MessageModel;
Debug.WriteLine("active filter @FilterFunction: " + Filter); //<------------------------ warum ist Filter hier immer (int)0
return Filter.HasFlag(message.Target);
}
Die Enumeration dazu sieht wie folgt aus:
[Flags]
public enum MessageTypeEnum
{
//[Description("Kein Filter")]
//None = 0,
[Description("Anwendung")]
Application = 1,
[Description("Distributor")]
Distributor = 2,
[Description("Setup")]
Setup = 4,
[Description("Debugger")]
Debug = 8,
[Description("Baumer Simulator")]
BaumerSimulator = 16,
[Description("Wolke Simulator")]
WolkeSimulator = 32,
}
Einer dieser Enumerationwerte wird dann ausschließlich hier ergänzt / entfernt:
_FilterItemClickCommand = new DelegateCommand<object>((s) =>
{
var _typeOfMessage = (MessageTypeEnum)s;
switch (Filter.HasFlag(_typeOfMessage) == false)
{
case true:
Filter |= _typeOfMessage; break;
default:
Filter &= ~_typeOfMessage; break;
}
Debug.WriteLine("active filter @CommandExecute: " + Filter);
View?.Refresh();
},
(s) => { return true; });
}
Schalte ich nun nacheinander die Filter "Application, Distributor, Setup und Debug" an, sieht die Ausgabe so aus:
[Debug.WriteLine Ausgabe]
active filter @FilterFunction: 0
active filter @FilterChanged: Application
active filter @CommandExecute: Application
active filter @FilterFunction: 0
active filter @FilterChanged: Application, Distributor
active filter @CommandExecute: Application, Distributor
active filter @FilterFunction: 0
active filter @FilterChanged: Application, Distributor, Setup
active filter @CommandExecute: Application, Distributor, Setup
active filter @FilterFunction: 0
active filter @FilterChanged: Application, Distributor, Setup, Debug
active filter @CommandExecute: Application, Distributor, Setup, Debug
Frage: Warum ist die "Filter" - Property in der Funktion "Filter_Function" immer 0 und macht den Filter damit wirkungslos ?
Hallo,
ich nutze im WPF DataGrid das SourceUpdated Event um Änderungen
am Benutzer (PMUser) zu validieren:
<DataGrid...
Style="{Controls:StaticResource MahApps.Styles.DataGrid.Azure}"
SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding Path=PM_UserList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SourceUpdated">
<i:InvokeCommandAction Command="{Binding DataSourceUpdatedCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Username, Mode=TwoWay, UpdateSourceTrigger=LostFocus, NotifyOnSourceUpdated=True}" Width="100"/>
<DataGridComboBoxColumn Header="Level" ItemsSource="{Binding Source={StaticResource UserRoleProvider}}" SelectedItemBinding="..."/>
<DataGridTextColumn Header="HasValidationError" Binding="{Binding HasInvalidData, Mode=OneWay}"/>
PMUser implementiert hier INotifyPropertyChanged wie folgt:
public class MySQLNotifier
{
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangedEventHandler SQLChangedDataPropertyChanged;
public void OnPropertyChanged(string prop)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
public void OnSQLDataPropertyChanged(string prop)
{
if (IsInitialized)
HasChanged = true;
SQLChangedDataPropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
}
public class PMUser : MySQLNotifier, IPMValidation
{
private ObservableCollection<App_ErrorEnum> _ValidationErrors = new ObservableCollection<App_ErrorEnum>();
public ObservableCollection<App_ErrorEnum> ValidationErrors
{
get { return _ValidationErrors; }
set
{
_ValidationErrors = value;
OnPropertyChanged(nameof(ValidationErrors));
}
}
private bool _HasInvalidData = false;
public bool HasInvalidData
{
get { return _HasInvalidData; }
set
{
_HasInvalidData = value;
OnPropertyChanged("HasInvalidData");
}
}
private int _ValidationErrorCount = 0;
public int ValidationErrorCount
{
get { return _ValidationErrorCount; }
set
{
_ValidationErrorCount = value;
OnPropertyChanged("ValidationErrorCount");
}
}
private string _Username;
public string Username
{
get { return _Username; }
set
{
_Username = value;
OnSQLDataPropertyChanged("Username");
OnSQLDataPropertyChanged("IsCurrentUser");
}
}
private UserRoleEnum _Userlevel;
public UserRoleEnum Userlevel
{
get { return _Userlevel; }
set
{
_Userlevel = value;
OnSQLDataPropertyChanged("Userlevel");
}
}
...
public void
Validation_Add(App_ErrorEnum newError)
{
ValidationErrors.Add(newError);
}
public void
Validate()
{
/* clear old validation */
ValidationErrors.Clear();
/* check empty username */
if (string.IsNullOrEmpty(Username))
Validation_Add(App_ErrorEnum.EmptyUsername);
else
{
PMUser _found = SQLConnector.Get_PMUser(Username);
if (_found != null)
{
/* new user: username exist */
/* old user: new username exist */
if (IsNew || SQL_ID != _found.SQL_ID)
Validation_Add(App_ErrorEnum.UsernameAlreadyExist);
}
}
...
HasInvalidData = ValidationErrors.Count > 0;
ValidationErrorCount = ValidationErrors.Count;
ValidationString = string.Empty;
foreach (var item in ValidationErrors)
ValidationString += item + " ";
}
PMUser.Validate() wird nun über das erwähnte SourceUpdatedEvent im UserViewModel ausgeführt:
private void On_DataSourceChanged(DataTransferEventArgs args)
{
var changedUser = ((FrameworkElement)args.OriginalSource).DataContext as PMUser;
if (changedUser == null)
return;
/* validate user data */
changedUser.Validate();
[breakPoint]
}
Über den BreakPoint erkenne ich, dass die Validierung erfolgt ist sobald bspw. username = string.Empty:
[changedUser ValidationErrorCount = 1, ValidationErrors = Count = 1, HasInvalidData = true, ValidationString = "EmptyUsername "]
Das Problem:
Im DataGrid werden diese Columns nicht aktualisiert (zeigen 0, bzw. false), obwohl das Model die korrekten Werte enthält.
<DataGridTextColumn Header="HasValidationError" Binding="{Binding HasInvalidData, Mode=OneWay}"/>
<DataGridTextColumn Header="ValidationErrorCount" Binding="{Binding ValidationErrorCount, Mode=OneWay}"/>
Diese DEBUG-Column zeigt hierbei immer den korrekten Zähler an Validation - Errors.
<DataGridTextColumn Header="ECount" Binding="{Binding ValidationErrors.Count, Mode=OneWay}"/>
Was übersehe ich?